Spring 中的常用定式

统一异常处理

对于 Restful API 来说,就是通过 RestCongtrollerAdvisor 注解实现。
对于 Spring MVC 来说,通过继承 ErrorController 实现 handleError()来实现统一 excepiton 处理。

就 Rest API 应用来说,一般把响应统一封装,常见的是封装成 BaseResponse of T。这种范型方式的使用对于 Feign 这种方式的 API 来说存在问题。
因为 Feign 是通过使用动态代理来执行请求 HTTP 服务、转换响应到对象的。而范型在运行时类型信息被擦除,所以 BaseResponse 不能和 Feign 一起使用。

对于 BaseResponse,基本就是封装三个元素:resultCode、message、data、(exception)。对于 resultCode 可以结合 javax.servlet.http.HttpServletResponse 中定义的状态码来定义。

参数校验

Feign 强类型客户端

使用 Feign 基本就是两个步骤:
1)在启动类标注 @EnableFeignClient
2)在客户端类标注 @FeignClient(SERVICE_NAME) 并在方法名上标注@RequestMapping。
Feign 的实现就是通过使用动态代理来执行请求 HTTP 服务、转换响应到对象。也算是强类型的一种客户端。

Profile

利用线程池的异步处理

Swagger / RestDoc

Gradle 101

  1. 确保团队使用统一的 gradle 版本。
    避免 Gradle 的版本兼容性问题,项目中应使用 gradldw 命令运行。因为 gradlew 会根据 gradle-wrapper.properties 定义的 gradle 分发 dizhi 去下载 gradle 到本地。

    在 build.gradle 中定义如下 task,生成两个文件:gradlew、gradlew.bat,一个目录:gradle。

     task genWrapper(type: Wrapper){
       gradleVersion='5.3.1'
       distributionUrl='https://services.gradle.org/distributions/gradle-5.3.1-all.zip'
       distributionBase='GRADLE_USER_HOME'
       distributionPath='wrapper/dists'
       archiveBase='GRADLE_USER_HOME'
       archivePath='wrapper/dists'
     }
    

    对于不能访问互联网的内网,需要保证 distributionUrl 地址在私服上可用。执行 gradle genWrapper 后,把生成的内容作为项目文件提交到代码库。

    这样后面的同事,checkout 出代码后遵循使用 gradlew 构建项目的规则就可以避免 gradle 版本冲突的问题了。

  2. Spring Boot 2.4 gradle 插件是需要 Gradle5.6 以上 或 Gradle6.3 版本以上的,否则报错如下。这也从一个方面证明 gradle 版本兼容问题是存在的,所以很有必要在团队中使用 gradlew。

    An exception occurred applying plugin request [id: ‘org.springframework.boot’, version: ‘2.4.0’]

    Failed to apply plugin [id ‘org.springframework.boot’]
    Spring Boot plugin requires Gradle 5 (5.6.x only) or Gradle 6 (6.3 or later). The current version is Gradle 5.3.1

Reference:

基本元素:
https://www.cnblogs.com/itck/p/10478382.html
https://blog.csdn.net/qq_36850813/article/details/93996333

wrapper:
https://www.cnblogs.com/davenkin/p/gradle-spring-boot.html

开发环境setup

在 macOS 上安装 OpenJDK。

brew tap AdoptOpenJDK/openjdk
brew search /adoptopenjdk
brew cask install adoptopenjdk8

设置 Homebrew 使用国内源,加速 update

cd /usr/local/Homebrew
git remote set-url origin http://mirrors.ustc.edu.cn/homebrew.git

变更 Win10 上基于 WSL2 的 Docker 镜像位置

1)停止 Docker desktop
2)停止 WSL

  wsl --shutdown

3)迁移

wsl --export docker-desktop-data D:\docker\docker-desktop-data\docker-desktop-data.tar
wsl --unregister docker-desktop-data
wsl --import docker-desktop-data D:\docker\ D:\docker\docker-desktop-data\docker-desktop-data.tar --version 2

运行 MySQL docker image

docker pull mysql:8.0.22
docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 \
 -v /home/mysql/conf/my.cnf:/etc/mysql/my.cnf \
 -v /home/mysql/logs:/logs \
 -v /home/mysql/data/mysql:/var/lib/mysql \
 -d mysql:8.0.22

设置 MySQL root 用户可以远程登录

docker run -it --rm mysql bash
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

运行 MySQL docker image

docker run -p 6379:6379 –name redis -v /Users/dahui/docker_env/redis/redis.conf:/etc/redis/redis.conf -v /Users/dahui/docker_env/redis/data:/data -d redis redis-server /etc/redis/redis.conf –appendonly yes –requirepass “password”

–appendonly yes : 开启 redis 持久化
–requirepass “password” : 密码

docker run –name redis -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 \n -v /Users/dahui/docker_env/mysql/conf/my.cnf:/etc/mysql/my.cnf \n -v /Users/dahui/docker_env/mysql/logs:/logs \n -v /Users/dahui/docker_env/mysql/mysql/data/mysql:/var/lib/mysql \n -d mysql:8.0.22

Reactive Streams规范及常见库

什么是Reactive Streams:

Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.

概括的说,Reactive Streams 是个(Java 世界里的)规范,它规范了“有非阻塞背压机制的异步的流处理”。挺简单的定义,但是能够真正正确理解异步、非阻塞并不容易,以后单独开写一篇。实际上 Reactive Streams 规范或者说其实现包含的内容更加丰富:除了 non-blocking,还有:Composable、Deferred、Flow Controll、Resilient、Interruptible。

其中 Composable 就是函数式编程思想的用武之地。 可体会下 Java8 里的 Stream API 各种算子的参数,所以 Lamda 表达式是进行 Reactive Streams 实现的基本前提,否则很难想象臃肿的面向对象的 Composable。有了 JDK8 的铺垫,Reactive Streams 接口被 JDK9 定义在 Flow 里才是可能的。

As of August 23rd, 2019 we have released version 1.0.3 of Reactive Streams for the JVM, including Java API, a textual Specification, a TCK and implementation examples.

这个规范由三部分组成:Java API(org.reactive-streams)、以文字描述的规范、技术兼容工具包。
Reactive Streams 规范 仅限于 Java(JavaScript、网络协议)世界,其它语言虽然也有 Reactive 这样的工具(参考这里: ReactiveX)实现,但好像没有类似的规范。

为什么要有 Reactive Streams

因为很多厂商开发了 reactive 库,但是它们直接很难/不可能互操作。用 Reactive Streams 进行规范就使得它们可以互操作,也就让它们串起来形成一个 reactive 链成为了可能。

为什么要用 Reactive

因为 reactive 可以榨干 CPU…,所以从老板的角度讲是省钱、从环保的角度讲是省电、从码农的角度讲是有意思。

Java 世界里的 Reactive 库

RxJava

从 Reactive 宣言、到 Reactive Streams 规范,再到各种 Reactive 库是很自然的一个脉络。
但现实是大家先有了 Reactive 系统的思想,聪明的程序员开发出各种蕴含着 reactive 思想的库(比如 RxJava 1.0)。为了各个库之间的统一性、可操作行,大家一起协商出了 Reactive Streams 规范。继而这些已经存在的 reactive 库便改进自己的 API 设计,向 reactive streams 规范靠拢并提供各种转化 api 让用户在原生 api 和 reactive streams 接口直接转换。比如 RxJava 2.0 的 Flowable 就直接继承自 org.reactive-streams.Publisher 并提供了 toObservable() toFlowable()。因为各个库的实现细节不同,用到具体转换 api 需要参考其手册。

JDK

这个规范被的 API 形式定义从 JDK 9 这个版本开始,以 java.util.concurrent.Flow 静态子类的形式被定义。其实,既然已经有了 org.reactive-streams 这样的规范,为什么还要在 JDK 中弄出个 Flow 来再重新定义一次。难道就是要宣示 JDK 自身有支持 reactive streaming 的东西?这个思路的本意应该就像 JDBC 接口一样,让 Flow 里定义的接口成为 SPI,让不同的 reactive sreams 库供应商可互操作吧。所以 JDK 里的 Flow 中定义的东西不能算是库,而是个 SPI:Service Provider Interface。

Vert.x、MongoDB 响应式流驱动

这些都做了改进以符合 org.reactive-streams 中的 API 定义。其中Vert.x不仅提供了对 java 的 reactive 库,还有 JavaScritp、Ruby、Scala 等。

Spring 的 Project Reactor

Project Reactor 主页:https://projectreactor.io/docs/core/release/reference/
既然 Spring 都提供了对 Reactive Streams 的实现,感觉其实上面列出的几个库已经没有太多的意义。各家对 Reactive Streams 规范的实现在细节上都有很大不同,因为 Spring 的生态太强大了,如果没有特殊的需求,比如 JDK 小于 8,那么我们的项目基本于 Project Reactor,那么应该是较好的选择。这个网页 https://tech.io/playgrounds/929/reactive-programming-with-reactor-3/Intro 可以帮助操练 Reactor Api。

Project Reactor 到目前为止经历了 1.0, 2.0, 3.3。其中 1.0 这个阶段还没有 Reactive Stream 是规范。在 2.0 开始 follow 规范并基本定型。3.0 感觉是个重构版,形成 reactive-streams-commons 库。这里是 3.0 的 release 文档: https://github.com/reactor/reactor-core/releases?after=v3.0.0.RELEASE

有了 Project Reactor 这样的基础库,整个 Spring 组件基本都有了 Reactive Style 的版本,再这个基础上
用 Netty(或 Servet 3.1 Containe)+ Reactive Streams 适配层 + Spring Security Reactive + WebFlux + Spring Data Reactive Repository,就可以构建出重头到尾的 Reactive 应用。

从 Spring Cloud 的组件角度讲,也衍生出 Reactive Discovery Client, Reactive Load Balancer, Blockhound, Reactor Debug, Improved Reactor Micrometer Support, Reactor Netty Metric …

Stephane 是实现 Project Reactor 的主力,下面是他的两个视频:​
https://www.youtube.com/watch?v=Tr04KiJdAXQ
https://www.youtube.com/watch?v=zls8ZLry68M

其它编程语言的 Reative 库

其它编程语言的 Reative 库可参考:https://github.com/ReactiveX
另,Akka Stream:https://doc.akka.io/docs/akka/current/stream/stream-introduction.html

Reference:
https://www.reactive-streams.org/
https://www.youtube.com/watch?v=Cj4foJzPF80&list=PLVoESeXbuo-yWN1HW5g45v6eGEPOcfWU0&index=4&t=2940s

从观察者模式到reactive stream规范

Reactive 的系统就是通过实现回弹性、弹性能够对客户请求进行及时响应的系统。

正如 Reactive Manifesto 说的:

Systems built as Reactive Systems are more flexible, loosely-coupled and scalable.

说到松耦合,最基本的模式之一就算是“观察者模式”了。从观察者模式更进一步,可以衍生出“发布订阅模式”。较之前者,发布订阅模式多了一个‘事件通道’的角色。通过这个通道,使得订阅者不必知道发布者——即两者解耦。Spring 在应用层面对“发布-订阅”模式进行了不错的支持(同步的、异步的),诸如@EventListener 等的日常用法参见:
https://www.baeldung.com/spring-events
https://reflectoring.io/spring-boot-application-events-explained/

响应式流 = 观察者 + 迭代器

从 Iterator 定义看,这是一个“拉”模型,当我们需要数据时通过调用 next()拉取,hasNext()是查询是否还有数据可用。如果上游数据有什么异常情况,只能是通过 next()抛出的异常感知甚至感知不知道。
public interface Iterator {
E next();
boolean hasNext();
}

以 RxJava 的 RxObserver 为例,感受下其接口语义上的改进:

public interface RxObserver<T> {
  void onNext(T next);
  void onComplete();
  void onError();
}

以 on 形式的前缀开头的方法名让我很明显的感受到一种“通知”的味道。上游组件告诉我们有新数据了、结束了、出错了!一种“推”的味道。

演进到这一步是不是就完美了呢?不。
如果 RxObserver 与上游组件是跨网络通信,那么我们可以想象每次的 onNext 通过网络一次处理一个数据的这种模式并不高效。而且 RxObserver 不能想上游表达自己的需求(比如需要几个数据?不再需要数据等),这就很容易延伸到 RxObserver 作为消费者的处理速度与上游不匹配时如何与上游协调工作这样的问题。
Reactive Stream 规范对以上需求进行了标准化,如下。其中 Subscription 就可用于向上游 Publisher 表达是否还需要、需要多少数据这样的请求。如果每次 request(1),整个模式就相当于“拉”模型;request(Integer.MAX_VALUE)就相当于“推”模型了。

public interface Publisher<T> {
    public void subscribe(Subscriber<? super T> s);
}

public interface Subscriber<T> {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

public interface Subscription {
    public void request(long n);
    public void cancel();
}

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

Reference:

https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md

http://reactivex.io/

A Playful Introduction to Rx by Erik Meijer

https://www.baeldung.com/spring-events
https://reflectoring.io/spring-boot-application-events-explained/

关于 Reactive Manifesto

反应 or 响应?还是反应吧。Reactive Manifesto 的中文版都已经是“反应式宣言”了。看来公众号也要择机改名了。

反应式宣言英文版:https://www.reactivemanifesto.org/
反应式宣言中文版:https://www.reactivemanifesto.org/zh-CN

前几年开始看到这些没有一行代码的宣言感觉好飘渺,但是随着自己看的代码、用的代码慢慢多起来,感觉这个宣言蛮不简单的。说上去就是这个几个词、几句话,但在实际项目中往往都对应着某个解决方案的思想原则。

反应式

反应式宣言最关键的四个单词:Responsive、Resilient、Elastic、Message Driven。
其中Reponsive是我们的终极目标,我们理想的系统是能对用户的请求尽可能地做出及时的响应。因为一个系统如果没有响应了基本就意味挂了,因此能够及时响应用户的请求是一个系统健康运行的标志。
系统要满足可响应的特性,那么这个系统就应该能够有:“回弹性”Resilient、“弹性”Elastic

Resilient——回弹性,强调的是系统的子模块出现问题时系统仍旧能够响应客户请求。

回弹性是通过复制、 遏制、 隔离以及委托来实现的。

举个例子来说“隔离”,Spring Cloud 的 Hystrix 断路器模式就是使用了隔离的思路。被@HystrixCommand 标注了的方法实际上是被 spring 动态生成的代理包装并由一个单独的线程池上的线程驱动运行。这样如果被@HystrixCommand 标注的方法出现了问题,不会直接影响调用方。系统框架从更高的层次就能感知到这个调用失败从而进行必要的处理。

Elastic——弹性,强调的是系统的负载发生变化时系统能够响应客户请求。之所以这里不用 Scalable,是想强调这里的负载可以是变大也可以是变小。

Message Driven——消息驱动,因为消息驱动的特点,它是天然实现回弹性、弹性的技术手段。

Reactive Systems rely on asynchronous message-passing to establish a boundary between components that ensures loose coupling, isolation and location transparency.

反应式系统依赖异步地消息传递机制在组件间建立边界,这就保证了组件之间的松耦合、隔离、以及位置透明。【感觉官方中文版翻译的有问题 :)】

组件之间的松耦合、隔离、位置透明是实现回弹性、回弹的条件。消息驱动是实现这些特性的很好的技术手段,但不是唯一的手段,比如就位置透明这点来说:Spring Cloud 的服务就是通过服务注册[使能 Eureka 服务、在服务的 bootstrap 文件中把服务注册到 Eureka]、服务发现[1, 原始的 EnableDiscoveryClient 到 2,@LoadBalanced+直接使用服务名的 rest template exchange]来对客户端实现了位置透明性。

从 Message Driven(异步非阻塞的消息)这点来讲,AKKA 才是最优的解决方案。

安装k8s的Dashboard

安装 Dashboard 相较之前的步骤就容易很多了。

下载 Dashboard yaml

这里使用 Dashboard 2.0.4。

wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.4/aio/deploy/recommended.yaml

vi recommended.yml

# 设置node port,让集群外部通过node port可以访问Dashboard
    nodePort: 30443
type: NodePort

Dashboard Home Page

安装 Dashboard

kubectl apply -f recommended.yaml
kubectl get pods -n kubernetes-dashboard

验证

此时访问 http://master_node_ip:30443 并用 kubeadmin init 输出的 token 可以登录到 Dashboard 管理界面。

创建 Dashboard 专用登录用户

# 创建用户 devops
kubectl create serviceaccount devops -n kube-system

# 授权
kubectl create clusterrolebinding devops --clusterrole=cluster-admin --serviceaccount=kube-system:devops

kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/devops/{print $1}')

访问 http://master_node_ip:30443 并用 devops 的 token 登录。

kubelet up

使用kubeadm创建K8S单控制节点集群

在对 K8S 控制节点有 HA 方面需求的话,应考虑 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/ 提到的两种拓扑。

HA 的方式除了让 controll nodes 和 etcd 有多个节点外,另外一个就是对外需要 VIP + LoadBalance 的功能。https://github.com/kubernetes/kubeadm/blob/master/docs/ha-considerations.md#options-for-software-load-balancing 讲述了如果使用 keepalived 和 haproxy 来实现这个前提。这个连接讲述了三个部署 keepalive, haproxy 的方式。推荐还是使用第二或第三种,第一种方式仅作为我们理解背后工作的逻辑就好。

因为这里只是开发环境使用的 k8s,所以就不以 HA 的方式安装 k8s 了。。无非就是多了两个步骤:
1)准备 VIP/LB;
2)kubeadm –init 时,用–control-plane flag 添加其它 CP 节点而已)。

1)为 kubeadm 准备虚拟机和 docker

准备开发用 KVM HOST 并 创建网桥

步骤参考:
a) 准备 kvm:https://dhyuan.github.io/2020/09/19/devops/create_gust_hosts_on_kvm_by_ansible/
b) 创建网桥:https://dhyuan.github.io/2020/09/21/devops/create_bridge_on_centos7/

下载创建虚拟机的 CentOS7 镜像

curl -O https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2

sudo mv -iv CentOS-7-x86_64-GenericCloud.qcow2 /var/lib/libvirt/images/

得到一键安装 GuestHost 和 Docker 的 ansible 脚本。

注意这里是使用分支 devenv 上的代码。

cd ~/devenv_bootstrap
git clone -b devenv https://github.com/dhyuan/virt-infra-ansible.git

cd ~/devenv_bootstrap/virt-infra-ansible/roles
git clone -b devenv https://github.com/dhyuan/ansible-role-virt-infra

ansible-galaxy install \
--roles-path ~/.ansible/roles/ \
git+https://github.com/haxorof/ansible-role-docker-ce.git,2.7.0

根据自己的需求修改 K8S master、nodes 节点的主机配置。

注意:节点名称已经改为使用-而不是_以符合 DNS 规范。这点是 K8S 的要求,但是和 yaml 的格式规范使用下划线有点而冲突。

cd ~/devenv_bootstrap
vi inventory/k8s-masters.yml
vi inventory/k8s-nodes.yml

一键创建 master + nodes 虚拟机

ansible-playbook ./virt-infra.yml \
    --limit kvmhost,k8s-masters,k8s-nodes

一键在 master、nodes 节点上安装 docker

ansible-playbook ./install_docker.yml \
--limit k8s-masters,k8s-nodes -v \
-e '{"docker_repository_url": {"CentOS": "http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo"}}'

验证环境

在 KVM HOST 查看/etc/hosts 里面记录了各个主机节点 IP。
各节点互相 ping
登录到各主机检查 docker 引擎是否工作正常。

2)安装 kubeadm,此步骤需要在各个 master、nodes 节点执行。

下面基本参照官网 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ 以及另阿良的
https://mp.weixin.qq.com/s/8JznAEKe6b-wE-kNx7_Svg 来进行。

设置 kubernets yum 源

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
# 使用阿里源
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg

enabled=1
gpgcheck=0
repo_gpgcheck=0
exclude=kubelet kubeadm kubectl
EOF

确保将桥接的 IPv4 流量传递到 iptables 的链:

加载 br_netfilter 模块加载:

sudo modprobe br_netfilter
lsmod | grep br_netfilter

设置 br_netfilter,也可设置在/etc/sysctl.d/99-sysctl.conf 文件里。

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-iptables=1
EOF

sudo sysctl --system

设置禁用 SELinux

sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config

让各机器时间同步

因为这里的环境是运行在 KVM 里的虚拟机,所以这一步可忽略。在生成环境中,各机器时间应该同步。

yum install ntpdate -y
ntpdate time.windows.com

安装 kubeadm

sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
yum list installed | grep kube

此时可用的最新版本是 1.19.2。

enable kubelet

sudo systemctl enable kubelet
sudo systemctl status kubelet

此时 kubelet 的状态是 failed,因为 api server 还没有起来。

3)构建集群

在规划的 master 节点上首先初始化一个集群的 master 节点

这里要注意因为 gcr.io 被墙, 所以是使用 ali 镜像。各参数含义参考:https://kubernetes.io/zh/docs/reference/setup-tools/kubeadm/kubeadm-init/
另外,关于 pod-network-cidr 的意义这篇文章非常好:https://blog.csdn.net/shida_csdn/article/details/104334372
执行 kubeadm 时需要下载镜像,所以需要稍等一会儿。

sudo kubeadm init \
--apiserver-advertise-address=192.168.1.11 \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.19.2 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16

kubeadm init 命令输出大致如下,并且 kubelet 服务也已经正常。

[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.11:6443 --token 67y1i4.686gqk1w73isp3op \
    --discovery-token-ca-cert-hash sha256:ccecf99d92615da1c67878029d79ae7323daac45476168091281ac80ddf0571d
[devops@k8s-master-0 ~]$

执行以下命令,让普通用户也能执行 kubectl 命令:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

安装网络插件 flannel

wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 替换 quay.io 为国内镜像 quay.mirrors.ustc.edu.cn

kubectl apply -f kube-flannel.yml

# 验证网络插件安装成功
kubectl get pods -n kube-system

如果需要,可以删除刚才安装的 flannel 插件:

kubectl delete -f kube-flannel.yml
ip link
ip link delete cni0
ip link delete flannel.1

把 nodes 加入集群

在各个 node 节点运行以下命令:

sudo kubeadm join 192.168.1.11:6443 --token 67y1i4.686gqk1w73isp3op \
        --discovery-token-ca-cert-hash sha256:ccecf99d92615da1c67878029d79ae7323daac45476168091281ac80ddf0571d

在 master 节点,验证 nodes 都成功加入 K8S 集群:

[devops@k8s-master-0 ~]$ kubectl get nodes
NAME           STATUS   ROLES    AGE     VERSION
k8s-master-0   Ready    master   72m     v1.19.2
k8s-node-0     Ready    <none>   4m41s   v1.19.2
k8s-node-1     Ready    <none>   76s     v1.19.2
k8s-node-2     Ready    <none>   89s     v1.19.2
[devops@k8s-master-0 ~]$

至此,已经完成了 Kubernetes 集群的搭建。


Reference:

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm
https://mp.weixin.qq.com/s/8JznAEKe6b-wE-kNx7_Svg
https://my.oschina.net/u/3021599/blog/4308021
https://cloud.tencent.com/developer/article/1525487
https://galaxy.ansible.com/geerlingguy/docker
https://galaxy.ansible.com/
在 CentOS7 上运行个 DHCP Server:https://www.tecmint.com/install-dhcp-server-in-centos-rhel-fedora/
https://www.cnblogs.com/hellxz/p/11044012.html

建立本地docker镜像中心

以下是在本地建立 docker registry 的过程,避免每次去互联网拉取镜像以及各种墙的问题。

安装 docker

因为我们使用 registry 镜像安装镜像中心,所以需要首先安装 docker 引擎。

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl status docker

运行 docker-registry

sudo docker pull registry
sudo docker run –restart=always -d -p 15000:5000 -v /mnt/data/registry:/var/lib/registry registry

TODO:添加镜像。。。