赞
踩
大概了解一下 CI/CD
是啥子,其实之前做过这东西,但是没解释过。
持续集成
(Continuous Integration,CI) :代码合并构建部署测试都在一起,不断地执行这个过程,并对结果反馈。
持续部署
(Continuous Deployment,CD):部署到测试环境、生产环境
持续交付
(Continuous Delivery,CD):将最终产品发布到生产环境。
都有个持续,也就是说在不断重复做这件事情,说白了这东西最终的目的就是将项目更有效的部署 / 更新,他的流程大概是这样,以 java
为例,开发提交代码到版本仓库后,通过 jenkins
这个持续集成的软件进行代码的拉取、单元测试、代码的编译、和镜像的构建,一会会用到 jenkins
的 master-slave
架构,slave
可以分担 master
的任务,一会部署的 jenkins
也是在 k8s
集群中,就是一个 pod
,所以自动添加的 slave
节点就是一个 pod
,有任务触发时,jenkins
会自动创建一个 pod
来作为他的 slave
,这个 slave
会去完成代码的拉取、测试、编译、构建镜像、推送镜像到仓库,推到镜像仓库后就可以部署在你需要的地方了,部署完之后通过 ingress
或是 NodePort
发布你的应用,发布之后就可以访问了撒,流程图如下。
jenkins
会完成上图的所有步骤,其实之前做的 jenkins
也可以完成这一套操作了,但不是部署到 k8s
平台的,这次是针对 K8S
的,一会会涉及到几个 jenkins
插件,这几个插件会帮助我们将项目部署到 k8s
平台,要知道 jenkins
百分之 90 的功能都是由插件实现的,下面要配置的插件里比较复杂的可能就是 Pipeline
了,还好我有狗头,哈哈。
先说一下需要准备的环境吧,首先需要一个 k8s
集群且部署有 coredns
,这个是必须的,无论你是用啥子方式部署的,如果现在没有集群请参考这篇文章使用 kubeadm
快速部署一个集群,我还是用之前二进制安装的集群。
还需要一个准备一个镜像仓库,可以自建或是直接用各种云提供商的,自建的话建议用 Harbor
,其实我不太喜欢用自建的,非 https
,还得去改 docker
的配置文件且需要重启,我最讨厌重启了。
再就是准备一个代码仓库,这里直接用 git
了,也是目前比较主流的版本仓库,我顺便把 harbor
也装了,但估计不会用它来存储镜像,下面先把 Git&harbor
装了吧。
这个是用来存镜像的,我直接在 kubeadm
的 node
节点上部署了,一会把 git
也仍在这里吧,懒得去创建虚拟机了,Harbor
官方地址,目前有两种安装包,分别是在线安装和离线安装,我用的在线安装,因为离线安装包有点大,还需要 docker-compose
的支持,
- [root@kubeadm-node ~]# curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
- [root@kubeadm-node /]# wget https://storage.googleapis.com/harbor-releases/release-1.8.0/harbor-online-installer-v1.8.0.tgz
- [root@kubeadm-node /]# tar zxf harbor-online-installer-v1.8.0.tgz
- [root@kubeadm-node /]# cd harbor/
- [root@kubeadm-node /harbor]# ls
- harbor.yml install.sh LICENSE prepare
需要先初始化一下,
[root@kubeadm-node /harbor]# ./prepare
会下载一个镜像,目录里面多东西了,编辑一下 harbor.yml
改点东西,三处。
- hostname: 192.168.1.248 #访问harbor的域名,没域名写IP
- data_volume: /harbor #数据目录,改不改看你撒
- harbor_admin_password: Harbor12345 # 默认admin登陆密码,改不改随你
改完之后就可以安装了,直接执行 install.sh
就可以了,会下载 N
个镜像,下载完成之后会自行启动。
- [root@kubeadm-node /harbor]# ./install.sh
- [root@kubeadm-node /harbor]# docker-compose ps
访问一下,
部署还是比较简单的,随便推个镜像试试。
这个就比较烦人了,我特么实在是不想重启 docker
,算了,估计一会就不用了,用云提供商的吧,下面部署一下 Git
。
git
是目前比较主流的,先把这个包装一下吧,如果你用 SVN
自行安装配置一下吧,不是很麻烦,
[root@kubeadm-node ~]# yum -y install git
访问仓库的方式这里使用 ssh
方式来访问了,随便创建一个用户,然后设置一个密码。
- [root@kubeadm-node ~]# useradd rj-bai
- [root@kubeadm-node ~]# echo Sowhat? | passwd --stdin rj-bai
切换到刚刚创建的用户,创建一个目录,初始化一下作为代码仓库。
- [rj-bai@kubeadm-node ~]$ mkdir rj-bai.git
- [rj-bai@kubeadm-node ~]$ cd rj-bai.git/
- [rj-bai@kubeadm-node ~/rj-bai.git]$ git --bare init
- Initialized empty Git repository in /home/rj-bai/rj-bai.git/
- [rj-bai@kubeadm-node ~/rj-bai.git]$ ls
- branches config description HEAD hooks info objects refs
这就创建完了,具体怎么拉取这个代码,如下,在 master
上执行了,直接 git clone
就行了。
- [root@master-1 ~]# cd /tmp/ && ls
- metrics-server systemd-private-5afd87d103b74339a4fac02fdb472124-chronyd.service-FVUqYN
- [root@master-1 /tmp]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
- Cloning into 'rj-bai'...
- Warning: Permanently added '192.168.1.248' (ECDSA) to the list of known hosts.
- rj-bai@192.168.1.248's password:
- warning: You appear to have cloned an empty repository.
- [root@master-1 /tmp]# ls rj-bai/
提示目录时空的,随便创建一个文件提交一下
- [root@master-1 /tmp/rj-bai]# cat <<OEF >index.html
- > testing
- > OEF
- [root@master-1 /tmp/rj-bai]# cat index.html
- testing
- [root@master-1 /tmp/rj-bai]# git add .
- [root@master-1 /tmp/rj-bai]# git config --global user.email "your@email"
- [root@master-1 /tmp/rj-bai]# git config --global user.name "rj-bai"
- [root@master-1 /tmp/rj-bai]# git commit -m 'index'
- [master (root-commit) 8f27bc9] index
- 1 file changed, 1 insertion(+)
- create mode 100644 index.html
- [root@master-1 /tmp/rj-bai]# git push origin master
- rj-bai@192.168.1.248's password:
- Counting objects: 3, done.
- Writing objects: 100% (3/3), 215 bytes | 0 bytes/s, done.
- Total 3 (delta 0), reused 0 (delta 0)
- To rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
- * [new branch] master -> master
提交到了 master
分支,验证一下,这个目录删了,重新拉去一下,能拉到就说明没啥子问题。
- [root@master-1 /tmp]# rm -rf rj-bai/
- [root@master-1 /tmp]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
- Cloning into 'rj-bai'...
- rj-bai@192.168.1.248's password:
- remote: Counting objects: 3, done.
- remote: Total 3 (delta 0), reused 0 (delta 0)
- Receiving objects: 100% (3/3), done.
- [root@master-1 /tmp]# ls rj-bai/
- index.html
嗯,就是这样,莫得问题,还有就是每次都要输入密码,配一个秘钥就好了,我现在有了,我直接 copy
去了。
- [root@master-1 ~]# ssh-copy-id rj-bai@192.168.1.248
- [root@master-1 ~]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
- [root@master-1 ~]# ls rj-bai/
- index.html
这样就可以了,仓库和 Git
都准好了,暂时先不动他了,下面开始在 k8s
集群中部署 jenkins
。
jenkins 官网,既然是在 k8s
中部署就直接使用 docker
方式了,github 地址,官方说明有一个目录需要做持久化,也就是 /var/jenkins_home
,jenkins
所有的数据都是存在这个目录下面的,jenkins
还需要一个唯一的网络标识,也就是说需要有状态的去部署 jenkins
了,这里就不多 BB
了,直接用 StatefulSet
方式去部署,依旧使用 NFS
动态供给作为存储,NFS
的部署方法之前写过了,部署 jenkins
的 YAML
文件是使用官方的模板,现在直接 git clone
下来。
- [root@master-1 ~]# mkdir jenkins
- [root@master-1 ~]# cd jenkins/
- [root@master-1 ~/jenkins]# git clone https://github.com/jenkinsci/kubernetes-plugin.git
克隆完之后进入到这个目录。
- [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# pwd
- /root/jenkins/kubernetes-plugin/src/main/kubernetes
- [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# ls
- jenkins.yml service-account.yml
可以看到有两个文件,service-account.yml
文件是创建 RBAC
授权相关的东西,这个不要动,主要看一下 jenkins.yml
。
这里使用了 PV
的模板,是需要你提供 PV
自动供给的支持的,默认的类型是 anything
,目前我只有一个 NFS
的,也就是这个。
所以说一会创建的时候它默认就会用这个了,如果你有两种类型的存储且需要指定就按正常流程走指定就完了,申请磁盘空间这里我把它改成 5G
了,1G
实在是有点小,还有资源限制那里,限制最小 0.5
核 CPU&0.5G
内存,最大 1
核 1G
内存,说实话有点小,最起码内存调大一点,我这里最大限制都调成 2
了。
又看了一眼 Service
这里,访问方式用的是 Ingress
,你可以用 NodePort
方式去访问,我这里用 Ingress
方式去做了,好久没搞过 nginx-ingress
了,先把 tls
方法去了,hosts
随便改一下,我改完的如下。
如果你不想用 Ingress
方式就删除掉上图光标的位置的注释,type
改为 NodePort
且略过部署下面部署 nginx-ingress
的部分,我这里部署一下 nginx-ingress
。
还是直接用官方的模板,先 git clone
下来,进到这个目录。
- [root@master-1 ~/demo/nginx-ingress]# git clone https://github.com/nginxinc/kubernetes-ingress.git
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# pwd
- /root/demo/nginx-ingress/kubernetes-ingress/deployments
按着官方的步骤这样部署,当然有些暂时用不到的步骤我直接省略了,官方文档
1. 创建命名空间和服务账户。
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/ns-and-sa.yaml
- namespace/nginx-ingress created
- serviceaccount/nginx-ingress created
2. 使用 TLS 证书和 NGINX 中默认服务器的密钥创建密钥
说白了就是给 nginx
一个默认的 TLS
证书,这个证书也是他们自签的,不受信任。
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/default-server-secret.yaml
- secret/default-server-secret created
3. 创建用于自定义 nginx 的配置文件
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/nginx-config.yaml
- configmap/nginx-config created
4. 配置 RBAC
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f rbac/rbac.yaml
- clusterrole.rbac.authorization.k8s.io/nginx-ingress created
- clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress created
5. 部署 ingress 控制器
这里的话用两种方法,分别为 deployment&&DaemonSet
,我这里直接用 DaemonSet
方式去部署了,具体为什么,官方说明,翻译内容如下。
如果您创建了 DaemonSet
,则 Ingress 控制器容器的端口 80 和 443 将映射到运行容器的节点的相同端口。要访问 Ingress 控制器,请使用这些端口以及运行 Ingress 控制器的群集中任何节点的 IP 地址。
省得再去创建 Services
了,看了一下部署控制器的文件,args
里面启用了两个,还有 6 个没启用,我翻译了一下,结果如下。
好像暂时没啥子能用到的,查看状态的可以考虑开启,我这里暂时就不开启了,直接创建了。
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f daemon-set/nginx-ingress.yaml
- daemonset.extensions/nginx-ingress created
- [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl -n nginx-ingress get pod
这样就可以了撒,部署完了,所有 Node
节点都有运行一个 nginx-ingress
控制器,除了两个 master
节点,因为我没允许 master
运行 Pod
,Ingress
部署完了,现在可以部署 jenkins
了。
部署文件之前已经修改过了,所以直接创建就完了。
- [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# kubectl apply -f .
- [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# kubectl get pod -w
正常启动了,写个 hosts
就访问就可以了撒。
不用去看这个文件,pod
日志里有输出密码,直接看 pod
日志就行了。
[root@master-1 ~]# kubectl logs jenkins-0
之后就需要装插件了,不装任何插件,一会用啥子装啥子,
现在不装插件最主要的原因其实就是有些插件装了需要重启 jenkins
才可以,如果现在装完了插件完成后续操作后会回到 jenkins
主页面,但是 jenkins
主页面就是一片空白,需要重启 jenkins
才可以,当然 pod
是没有重启这个概念的,你得想办法把之前的 pod
弄死,k8s
帮你重新拉起一个之后就正常了,所以先不装插件,正常的话完成后续操作就可以看到主页面了。
现在 jenkins
是装完了,下面需要 jenkins
和集群融合一下了。
现在需要 jenkins
和 k8s
集成,需要用到一个名为 kubernetes
的插件,这个插件是用来动态创建代理的,也是自动创建 slave
端,slave
就是你添加的工作节点,这里不需要你手动添加了,在有任务的时候 jenkins
会自动创建 pod
作为自己的 slave
节点,master-slave
架构解决了 master
单点负载的问题,使其演变成了一个分布式的架构,其实 slave
节点就是运行了一个 jar
包,我之前也搞过这个,这个 jar
包启动后会去连接 master
接收任务。
目前 jenkins
是 k8s
中的一个 pod
,所以他动态创建的 slave
也是集群中的 pod
,master
和 slave
端通讯是通过 jnlp
协议进行的,在有任务的时候 jenkins
会请求 k8s
集群帮他创建 slave
(也就是 pod
) 来完成任务,任务完成后这个 pod
就会自动销毁,下次有需要再启,想实现这个功能就需要一个插件了撒。
暂时先装两个插件吧,名为 git&&kubernetes
,如果你用 SVN
请安装 Subversion
插件,点开系统设置→插件管理→available
搜一下这两个插件,安装就行了,勾上安装完成后重启,其实我只选择了上述的两个插件,其他的都是依赖。
装完之后需要配置一下这个插件,使 jenkins
支持 kubernetes
,点开系统管理→系统设置→拉到最下面,可以看到这个,直接点进去,Kubernetes
这里的话配这样就行了,这里写的都是 dns
名称。
测试连接莫得问题,凭据和 key
都不用到,都已经 rbac
授权了,如果是部署在集群外 key
那里就需要写 apiserver
的 CA
信息了,再然后就是配置 jenkins
这里了。
这样就可以了,如果你的 jenkins
在集群外 kubernetes
地址就不要写内部 DNS
名称了,key
那里需要配置 apiserver
的 CA
证书就可以了,下面还有一个添加 pod
模板,也就是这个,
这个东西就是定义如何创建 slave pod
的一个模板,你可以理解为这里就是一个 YAML
文件定义了怎么去创建 slave-pod
,只不过是通过 UI
的形式去配置,这一块也可以通过 Pipeline
脚本去定义,所以不需要在这里配置这个了,如果在这里配置你可能每次添加一个项目都要在这里配置一次,不方便管理,在 Pipeline
里面配置就比较方便了,所以目前只需要配置怎么连接 kubernetes
就可以了,这个差掉,保存退出就可以了,下面开始构建 slave
镜像。
做一个验证,都配置完了,到底能不能用,直接用流水线了,目前还没装这个插件,所以先装一下,插件名就是 Pipeline
这次装的比较多,让他装着吧,装完后自动重启,一会会写一个 Pipeline
脚本,以测试 jenkins
能不能在集群中动态创建 slave-pod
,这个是必需步骤,否则无法继续下去,这个 slave-pod
的镜像需要自己做一个,下面开始制作这个镜像,其实是有默认的镜像,但是不用他默认的,对于我来说功能不全,这个 slave
镜像会完成代码拉取、单元测试、代码编译、构建镜像、推送镜像的步骤,所以现在做的 slave
镜像要有这些功能,下面开始编写 Dockerfile
编写 Dockerfile
上文提到了这个镜像需要完成代码拉取,单元测试 (和现在没啥子关系),代码编译,镜像构建以及推送镜像,所以你的镜像需要支持这些功能才可以,所以 Dockerfile
如下,按着自己的情况更改吧,官方参考地址
- FROM centos:latest
- RUN yum -y install java-1.8.0-openjdk maven curl git subversion libtool-ltdl-devel && \
- yum clean all && \
- rm -rf /var/cache/yum/* && \
- mkdir -p /usr/share/jenkins
- COPY slave.jar /usr/share/jenkins/slave.jar
- COPY jenkins-slave /usr/bin/jenkins-slave
- COPY settings.xml /etc/maven/settings.xml
- RUN chmod +x /usr/bin/jenkins-slave
- ENTRYPOINT ["jenkins-slave"]
settings.xml
是 maven
的配置文件,这个文件怎么写建议咨询一下开发人员,我还是用我们私服的,用默认的也可以,可能下载依赖包会比较慢,默认地址是国外的,jenkins-slave
也是在参考地址直接 copy
过来的,也不贴了,slave.jar
自行下载吧,地址就是 $JENKINS_URL/jnlpJars/slave.jar
,我这里也不传了,都准备好了直接 build
就完了。
- [root@master-1 /data/docker/jenkins-slave-jdk]# ls
- Dockerfile jenkins-slave settings.xml slave.jar
- [root@master-1 /data/docker/jenkins-slave-jdk]# docker build -t jenkins-slave-jdk:1.8 .
这样就构建完了,然后把这个镜像推送到镜像仓库,我不用刚刚搭建的 Harhor
,我直接推到 docker hub
去了,就是比较慢
- [root@master-1 /data/docker/jenkins-slave-jdk]# docker tag jenkins-slave-jdk:1.8 bairuijie/jenkins-slave-jdk:1.8
- [root@master-1 /data/docker/jenkins-slave-jdk]# docker push bairuijie/jenkins-slave-jdk:1.8
容器还需要构建和推送镜像,我并没有装在容器里安装 docker
环境,之后在启动这个 pod
的时候以数据卷的形式挂载宿主机的 docker
命令和 socket
进去就可以了,这只是一个适合拉取 git&svn
仓库代码和编译 java
代码的镜像,如果你是别的自行琢磨吧,这种问题多去问开发,下面来了解一下 Pipeline
Pipeline
就是一套插件,刚刚也安装了,上文提到的流程从拉取到部署都需要由 Pipeline
来完成,Pipeline
是通过特定的语法从简单到复杂的传输管道进行建模,支持两种定义的方式,一种为声明式,遵循 Grovvy
相同语法,使用 pipeline {}
,还有一种是脚本式,支持 Grovvy
大部分功能,也是表达灵活的工具,node {}
,这两种使用哪个都可以,看一下官方的栗子。
示例地址,
声明式
脚本式
我的头开始大了,后面主要是使用脚本式,这东西的定义就是一个文本文件,也称为 Jenkinsfile
,下面创建一个流水线任务来玩玩。
自行创建吧,创建完任务之后拉到最下面,选这个,会自动补全一个例子,改改这个例子。
改成这样。
这样就可以了,保存,然后构建一下,构建成功后你会看到这个。
可以看到刚刚定义的三个步骤以图表的形式展现了出来,这是一个最简单的示例,脚本里面都有多个 stage
,这个 stage
是脚本最基本的组成部分,它用来告诉 jenkins
要去干什么,之后就需要在这个脚本中实现从代码拉取到部署到 k8s
的全部过程,官方的原理图。
说白了还是完成了从构建到发布的流程,所以接下来就开始搞在脚本中完成这些操作。
第一步就是拉取代码,到底怎么拉,这些步骤的语法都可以通过 Pipeline
脚本语法去帮我们生成,这个是重点,也就是这个位置。
新到一个页面,譬如我现在要拉取 git
仓库 rj-bai.git
的代码,我就可以这样做了,
使用 master
分支,但是提示无法连接到这个仓库,这里也是需要免交互拉取代码的,之前的操作就是将 master
公钥拷贝到了 git
服务器,所以现在要用到私钥了,需要添加一个凭据,将用户名私钥添加进去添加即可,如果你是 SVN
请添加用户名密码,SVN
怎么拉代码下面会提到,
添加后回到主页面之后报错没了,说明可以免交互拉取代码了,点击 Generate Pipeline Script
之后就会生拉取代码的脚本了。
复制这一串子贴到这个位置就可以拉取代码了,我顺便执行了一条 shell
命令
保存构建,看了一下 log
输出,之前添加的 Index.html
已经拉取下来了,说明拉取代码这块莫得问题。
能拉取代码了,下一步就要开始编译了,下面看一下如何编译代码。
上面执行的这些任务都是在 jenkins pod
中去做的,现在还没有 slave
节点,一般编译 java
需要 maven
,maven
依赖 jdk
,不用看了,目前 jenkins
的 POD
上虽有 java
但没有 maven
,在上文的描述中拉取编译代码也是由 slave
去完成的,所以现在要启动 slave
了,既然是动态创建,就需要用到 pod
模板了。
启动测试 slave 节点
直接在 pipeline
脚本中定义吧,改完的 pipeline
脚本内容如下。
- podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
- containerTemplate(
- name: 'jnlp',
- image: "registry.cn-beijing.aliyuncs.com/rj-bai/jenkins-slave-jdk:1.8"
- ),
- ],
- volumes: [
- hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
- hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
- ],
- )
- {
-
- node ("jenkins-slave"){
- def mvnHome
- stage('拉取') {
- git credentialsId: 'efb4268b-f758-4b58-9dcd-d966faf8360e', url: 'rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git'
- sh 'ls -l'
- }
- stage('建构') {
- }
- stage('部署') {
- }
- }
- }
上面的那一段就是定义 slave
节点的声明,使用 jnlp
协议,镜像我传到了阿里云的仓库,这个是公开的,一会在构建的时候他回去拉这个镜像作为 slave
启动,我直接也把 docker
挂进去了,方便构建推送镜像,这样配置之后构建镜像就不是由 master
来完成的了,而是由动态创建的 slave
来完成的,测试一下,保存,然后构建,动态查看 pod
状态。
这里构建成功了,看一下 k8s
集群中 pod
的变化。
这就是动态创建 slave
了,在需要的时候创建一个,slave
完成任务后就会被销毁,这一块没啥子问题,下面准备一下要编译的源代码。
准备 java 源代码
既然是编译,你就需要有 java
的源码了,建议在这里生成一个,目前只要有简单的 web
访问就够了,所以选这个,编译成功后会有一个 jar
文件,jdk
版本 1.8
,这样就可以了。
选完点击绿色按钮你会下载一个名为 demo.zip
的压缩包,这就是源码了,然后在 git
上创建一个仓库,把代码提上去。
- [rj-bai@kubeadm-node ~]$ mkdir webstarter.git
- [rj-bai@kubeadm-node ~]$ cd webstarter.git/
- [rj-bai@kubeadm-node ~/webstarter.git]$ git --bare init
- Initialized empty Git repository in /home/rj-bai/webstarter.git/
仓库这里算是创建完了,然后去 master
提交代码。
- [root@master-1 ~]# git clone rj-bai@192.168.1.248:/home/rj-bai/webstarter.git
- [root@master-1 ~]# cd webstarter/
- [root@master-1 ~/webstarter]# unzip demo.zip ## 文件自行上传
- [root@master-1 ~/webstarter]# mv demo/* .
- [root@master-1 ~/webstarter]# rm -rf demo*
- [root@master-1 ~/webstarter]# git add .
- [root@master-1 ~/webstarter]# git commit -m 'webstarter'
- [root@master-1 ~/webstarter]# git push origin master
这样就可以了撒,其实 pipeline
的配置也可以写到一个名为 Jenkinsfile
的文件里,这个文件需要放在代码的根目录,这个后面会涉及到,下面开始配置编译代码的部分。
编译代码构建和推送镜像
编译代码这里需要 maven
去编译,构建镜像的话就是将项目包传到镜像里,一会会用 openjdk
的镜像,推送镜像就是将刚刚构建好的镜像推送到镜像仓库,既然涉及到了推送镜像就一定会给镜像打标签,具体这个标签怎么打,还是像之前那样,项目名 + 构建次数,所以脚本暂时写成这样,定义了 N 多变量。
- // 镜像仓库地址
- def registry = "registry.cn-beijing.aliyuncs.com"
-
- // 项目&镜像配置信息
- def namespace = "rj-bai"
- def app_name = "webstarter"
- def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
- def git_address = "rj-bai@192.168.1.248:/home/rj-bai/webstarter.git"
-
- // 认证信息ID
- def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
- def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
-
- podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
- containerTemplate(
- name: 'jnlp',
- image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
- ),
- ],
- volumes: [
- hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
- hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
- ],
- )
- {
- node("jenkins-slave"){
- stage('拉取代码'){
- checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
- }
- stage('代码编译'){
- sh "mvn clean package -Dmaven.test.skip=true"
- }
- stage('构建镜像'){
- withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
- sh label: '', script: ''' echo \'
- FROM openjdk:8
- ADD target/*.jar /
- ADD entrypoint.sh /
- RUN chmod +x /entrypoint.sh
- CMD ["/bin/bash","/entrypoint.sh"]
- \' > Dockerfile
- echo \'
- #!/bin/bash
- app=`ls /*.jar`
- java -jar $app
- \' > entrypoint.sh'''
- sh """
- docker build -t ${image_name} .
- docker login -u ${username} -p \"${password}\" ${registry}
- docker push ${image_name}
- """
- }
- }
- }
- }
你就当成 shell
脚本去看就完了,应该都能看的差不多,def
开头的就是定义的变量,Branch
是分支变量,现在还没定义,需要通过参数化构建过程去定义,其他的变量如果在这个脚本内找不到那他就是 jenkins
的内置变量,譬如 BUILD_NUMBER
,很久之前在别的文章里提过这个,先来解释一下我镜像仓库那里为什么这样写,看一下我镜像仓库完整的地址你就懂了。
如果你是自建的镜像仓库可以不加 namespace
的配置,我这里就必须得加了,还有一些奇怪的 ID
,下面分别来解释一下都是用来干嘛的。
docker_registry_auth
是用来拉取推送镜像的凭据,但是那里写的是凭据的 ID
,这个凭证是 slave-pod
所使用的,因为他需要向私有仓库推送镜像,需要登陆后才能去推送镜像,添加方法如下。
主页面→凭据→系统→全局凭据→添加,类型就是用户名密码,然后填进去点 ok
就可以了,ID
会自动生成一个。
添加之后会自动返回,然后点更新的按钮就可以查看到 ID
了,要和脚本中的对应上。
再下面的那个 git
认证之前就创建过了,自己查看 ID
改一下吧,再看一下拉取代码那里有很奇怪的一段,也就是这个,
checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
这个也是用来拉取代码的,之前提过一个使用 git
方式去拉取的,刚刚用的那种方式是这个,看下图,通过这个也可以拉取代码,而且推荐使用这个,地址 ID
分支信息上文都用的是变量,其他的都一样,SVN
代码也用这种方式去拉就行了,SVN
拉代码我建议用这个,
checkout([$class: 'SubversionSCM', locations: [[cancelProcessOnExternalsFail: true, credentialsId: "${svn_auth}", depthOption: 'infinity', ignoreExternalsOption: true, local: '.', remote: "${svn_address}"]], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])
在看构建镜像那里也有奇怪的一段,也就是这个,
withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
这一段是用来保存登陆镜像仓库的用户名和密码,以变量的形式,这样做的主要原因就是不会让用户名密码的明文暴露在 pipeline
脚本里,也是动态生成的,如果所示。
和我脚本中的一致,只不过我脚本中定义的是 ID
的变量,说白了这样做就是将用户名存到了名为 username
的变量中,密码保存到了 password
变量中,直接引用就完了,不会暴露你用户名密码明文,就是这个原理,再下面写了一个 Dockerfile
和一个启动脚本,现在还有一个变量没有去定义,也就是 Branch
,用来定义要构建的分支,git
的分支会有很多,不止是一个 master
,所以说不是固定的,所以现在定义一下这个变量。
编辑这个 job
,找到参数化构建过程,配置成这样,使用 SVN
的不用改这里撒,
这就是一个变量,默认值是 master
,保存回到主界面,点 Build with Parameters
,会看到这种效果。
中途失败了好多次,各种调试,之前也成功过,但是第 25 次是才是真正意义上的成功,
现在镜像已经推送到我阿里云的仓库了,直接在服务器上拉一下吧,试着运行一下。
[root@kubeadm-node ~]# docker run -d -p 666:8080 registry.cn-beijing.aliyuncs.com/rj-bai/webstarter:25
莫得问题,可以正常访问,说明之前的步骤都没问题了,现在通过 pipeline
完成了 CI
阶段,拉取代码编译构建镜像推送镜像,感觉是比之前的方便很多了,下一步就是需要将刚刚的东西部署到 K8s
中了,也就是 CD
阶段。
上面镜像已经准备好了,现在该实现自动部署到 k8s
中了,要想实现这个还需要一个插件,名为 kubernetes continuous deploy
,用于将资源部署到 k8s
中,他支持绝大部分的资源类型,像是什么 deployment&service
,自行安装吧,简单看一下这个插件的介绍。
官方地址,主要看一下这一段,这是配置在 pipeline
中使用的写法
kubeconfigId
这里是需要指定一个 kubeconfig
的 ID
,这个东西就是用于连接 k8s
的一个配置文件,所以我们要把这个文件内容保存到 jenkins
中作为凭据,然后去引用凭据的 ID
。
config
这里用来指定资源文件,也就是你部署服务的 YAML
文件。
- secretNamespace: '<secret-namespace>',
- secretName: '<secret-name>',
这块是用来指定 secret
的,有两个是必须的,一个是 kubeconfig
文件,再就是资源文件,这个插件的 pipeline
写法也是可以生成的,所以还是用之前的工具来生成一下。
现在开始让你添加 kubeconfig
的文件了,添加吧,选择这个,
这个文件具体要怎么获取,如果你的集群是 kubeadm
安装的获取很简单,就是这个文件。
[root@rj-bai ~]# cat .kube/config
把这个文件的内容复制出来保存到 jenkins
里面就可以了,如果你是二进制部署的集群,就要手动去生成这个文件了,这个文件默认是没有的,具体怎么生成这个文件之前写过,这里就不贴了,我使用的集群也是二进制方式部署的,我直接把之前生成的文件拿过来就直接用了,然后那个 config Files
就是指定资源文件了,这个文件就是用来部署我们的项目的,emmmm,写一个吧,我写的如下。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: web
- spec:
- replicas: 3
- selector:
- matchLabels:
- app: web-starter
- template:
- metadata:
- labels:
- app: web-starter
- spec:
- imagePullSecrets:
- - name: $SECRET_NAME
- containers:
- - name: web-starter
- image: $IMAGE_NAME
- ports:
- - containerPort: 8080
- name: web
- livenessProbe:
- httpGet:
- path: /favicon.ico
- port: 8080
- initialDelaySeconds: 30
- timeoutSeconds: 5
- failureThreshold: 3
- readinessProbe:
- httpGet:
- path: /favicon.ico
- port: 8080
- initialDelaySeconds: 30
- timeoutSeconds: 5
- failureThreshold: 3
- resources:
- requests:
- memory: "64Mi"
- cpu: "250m"
- limits:
- memory: "128Mi"
- cpu: "500m"
-
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: web
- spec:
- type: NodePort
- selector:
- app: web-starter
- ports:
- - protocol: TCP
- port: 80
- targetPort: 8080
- name: web
-
- ---
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: web
- spec:
- rules:
- - host: webstarter.rj-bai.com
- http:
- paths:
- - path: /
- backend:
- serviceName: web
- servicePort: 80
应该都能看懂,我部署了 nginx-ingress-controller
,所以就直接以 ingress
方式发布出去了,如果你没有部署 ingress
控制器就直接用 NodePort
吧,看一哈这个文件中有两个变量,一个是用来存放认证登陆信息的 secrets
名字,再一个就是镜像地址,一会使用 sed
去替换成相对应的值,暂时就定义这两个变量,其实还有很多可以定义,只要是经常变动的,像是什么健康检查端口绑定域名也可用变量,用变量的目的就是复用,自行琢磨吧,这个文件需要存在你 git
版本代码仓库中,自行上传提交一下吧,我的名为 deploy-webstarter.yaml
。
所以,最终的配置文件 pipeline
脚本如下。
- // 镜像仓库地址
- def registry = "registry.cn-beijing.aliyuncs.com"
-
- // 项目&镜像配置信息
- def namespace = "rj-bai"
- def app_name = "webstarter"
- def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
- def git_address = "rj-bai@192.168.1.248:/home/rj-bai/webstarter.git"
-
- // 认证信息ID
- def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
- def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
- def secret_name = "registry-secret"
- def k8s_auth = "9823945c-07f6-495c-bc48-a47d8f43536c"
-
- podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
- containerTemplate(
- name: 'jnlp',
- image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
- ),
- ],
- volumes: [
- hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
- hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
- ],
- )
- {
- node("jenkins-slave"){
- stage('拉取代码'){
- checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
- }
- stage('代码编译'){
- sh "mvn clean package -Dmaven.test.skip=true"
- }
- stage('构建镜像'){
- withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
- sh label: '', script: ''' echo \'
- FROM openjdk:8
- ADD target/*.jar /
- ADD entrypoint.sh /
- RUN chmod +x /entrypoint.sh
- CMD ["/bin/bash","/entrypoint.sh"]
- \' > Dockerfile
- echo \'
- #!/bin/bash
- app=`ls /*.jar`
- java -jar $app
- \' > entrypoint.sh'''
- sh """
- docker build -t ${image_name} .
- docker login -u ${username} -p \"${password}\" ${registry}
- docker push ${image_name}
- """
- }
- }
- stage('部署到K8S'){
- sh """
- sed -i 's#\$IMAGE_NAME#${image_name}#' deploy-webstarter.yaml
- sed -i 's#\$SECRET_NAME#${secret_name}#' deploy-webstarter.yaml
- """
- kubernetesDeploy configs: 'deploy-webstarter.yaml', kubeconfigId: "${k8s_auth}"
- }
- }
- }
增加了两个变量,一个是存 kubeconfig
的 ID
,一个是 secret
的名字,这个是干嘛的不用多说了,如果没有自行创建吧,使用 sed
替换了镜像地址和 secret
的名字,最后 kubernetesDeploy
配置的那里,现在就用到的就这两个,没用到的全部去掉了,开始跑吧。
开始构建,然后动态查看 pod
的状态,先看页面,显示成功。
再看 pod
的状态,都已经正常启动了,
然后手写 hosts
访问一下,
莫得问题,就是这种效果,现在也完成了持续部署,生成的 demo
就是一个简单的 web
,没有任何的页面,只有默认的 404
, 健康检查那里我检查的还是 favicon.ico
,反正是起来了,访问也没问题,过。
其实这个 pipeline
脚本的内容也可以放在项目的根目录,也就是和 deploy-webstarter.yaml
同级,不需要写在 jenkins
里,这样的话比较方便管理,再加项目的时候你只需要在 jenkins
上创建 job
直接引用仓库地址就可以了,现在就要达到这个目的,部署一个东西撒,一个名为 DimpleBlog
的 java
博客,感兴趣的去 github
上搜一下吧,编译好了也是一个 jar
包,拿到源码之后还是新建仓库,master
拉一下把代码将源码传到仓库,先不要提交到 git
中,需要改点东西。
刚刚提到了这是一个博客程序,要他运行起来需要 mysql&&redis
,他的 sql
文件是存在源码的 sql
文件夹里,我集群中刚好有一个 mysql
,也是之前创建的,创建方式我就不贴了,之前贴过,我看 sql
里用的是 test
表,我手动导入进去了,现在还莫得 redis
,创建一个 redis
出来吧,使用 StatefulSet
创建,和创建 mysql
的方式一致
- [root@master-1 ~/demo]# cat redis.yaml
- apiVersion: v1
- kind: Service
- metadata:
- name: redis
- spec:
- ports:
- - port: 6379
- name: redis
- clusterIP: None
- selector:
- app: redis
-
- ---
-
- apiVersion: apps/v1beta1
- kind: StatefulSet
- metadata:
- name: redis
- spec:
- updateStrategy:
- type: RollingUpdate
- serviceName: "redis"
- template:
- metadata:
- labels:
- app: redis
- spec:
- containers:
- - name: redis
- image: redis:latest
- ports:
- - containerPort: 6379
- name: redis
- volumeMounts:
- - mountPath: "/data"
- name: redis-data
- livenessProbe:
- tcpSocket:
- port: 6379
- initialDelaySeconds: 10
- periodSeconds: 3
- failureThreshold: 3
- volumes:
- - name: redis-data
- persistentVolumeClaim:
- claimName: redis-data
- volumeClaimTemplates:
- - metadata:
- name: redis-data
- spec:
- accessModes: [ "ReadWriteOnce" ]
- resources:
- requests:
- storage: 1Gi
- [root@master-1 ~/demo]# kubectl apply -f redis.yaml
- service/redis created
- statefulset.apps/redis created
- [root@master-1 ~/demo]# kubectl get pod redis-0
- NAME READY STATUS RESTARTS AGE
- redis-0 1/1 Running 0 3m10s
已经启动了,然后去改一下这个项目连接数据库的配置文件,这个位置,
- [root@master-1 ~/DimpleBlog]# vim src/main/resources/application-druid.yml
- # 主库数据源
- master:
- url: jdbc:mysql://mysql:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
- username: root
- password: Sowhat?
这是数据库的,还要改一下 redis
的,
- [root@master-1 ~/DimpleBlog]# vim src/main/resources/application.yml
- redis:
- host: redis
- port: 6379
- database: 0
这样就可以了撒,还需要创建资源文件和 jenkinsfile
,先把资源文件写了吧,按着之前的改改就行了,我改成这样。
- [root@master-1 ~/DimpleBlog]# cat deploy-dimpleblog.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: web-dimpleblog
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: dimpleblog
- template:
- metadata:
- labels:
- app: dimpleblog
- spec:
- imagePullSecrets:
- - name: $SECRET_NAME
- containers:
- - name: dimpleblog
- image: $IMAGE_NAME
- ports:
- - containerPort: 80
- name: web-dimpleblog
- livenessProbe:
- httpGet:
- path: /
- port: 80
- initialDelaySeconds: 60
- timeoutSeconds: 10
- failureThreshold: 5
- readinessProbe:
- httpGet:
- path: /
- port: 80
- initialDelaySeconds: 60
- timeoutSeconds: 10
- failureThreshold: 5
- resources:
- requests:
- memory: "512Mi"
- cpu: "0.5"
- limits:
- memory: "1024Mi"
- cpu: "1"
-
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: web-dimpleblog
- spec:
- type: NodePort
- selector:
- app: dimpleblog
- ports:
- - protocol: TCP
- port: 80
- targetPort: 80
- name: web-dimpleblog
-
- ---
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: web-dimpleblog
- spec:
- rules:
- - host: dimpleblog.rj-bai.com
- http:
- paths:
- - path: /
- backend:
- serviceName: web-dimpleblog
- servicePort: 80
这次健康检查时间调长了,资源文件有了,然后是 jenkinsfile
,放在和资源文件目录同级的地方,
- [root@master-1 ~/DimpleBlog]# cat Jenkinsfile
- // 镜像仓库地址
- def registry = "registry.cn-beijing.aliyuncs.com"
-
- // 项目&镜像配置信息
- def namespace = "rj-bai"
- def app_name = "dimpleblog"
- def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
- def git_address = "rj-bai@192.168.1.248:/home/rj-bai/DimpleBlog.git"
-
- // 认证信息ID
- def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
- def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
- def secret_name = "registry-secret"
- def k8s_auth = "9823945c-07f6-495c-bc48-a47d8f43536c"
-
- podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
- containerTemplate(
- name: 'jnlp',
- image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
- ),
- ],
- volumes: [
- hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
- hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
- ],
- )
- {
- node("jenkins-slave"){
- stage('拉取代码'){
- checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
- }
- stage('代码编译'){
- sh "mvn clean package -Dmaven.test.skip=true"
- }
- stage('构建镜像'){
- withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
- sh label: '', script: ''' echo \'
- FROM openjdk:8
- ADD target/*.jar /
- ADD entrypoint.sh /
- RUN chmod +x /entrypoint.sh
- CMD ["/bin/bash","/entrypoint.sh"]
- \' > Dockerfile
- echo \'
- #!/bin/bash
- app=`ls /*.jar`
- java -jar $app
- \' > entrypoint.sh'''
- sh """
- docker build -t ${image_name} .
- docker login -u ${username} -p \"${password}\" ${registry}
- docker push ${image_name}
- """
- }
- }
- stage('部署到K8S'){
- sh """
- sed -i 's#\$IMAGE_NAME#${image_name}#' deploy-dimpleblog.yaml
- sed -i 's#\$SECRET_NAME#${secret_name}#' deploy-dimpleblog.yaml
- """
- kubernetesDeploy configs: 'deploy-dimpleblog.yaml', kubeconfigId: "${k8s_auth}"
- }
- }
- }
也是按之前的改了改,这样就够了,然后提交代码,
- [root@master-1 ~/DimpleBlog]# git add -A
- [root@master-1 ~/DimpleBlog]# git commit -m 'master'
- [root@master-1 ~/DimpleBlog]# git push origin master
这里准备好了,接下来就去 jenkins
操作吧,新建流水线任务,还是要添加字符参数,上文写过了,不贴了,主要是看配置流水线那里,选择在代码仓库在去拉取 Jenkinsfile
,配置如下,
Branches to build
里指定的是要在哪个分支去拉取 Jenkinsfile
,我们这里指定 master
就可以了,这样配置后脚本就不用写在 jenkins
里了,保存开始构建吧,这次是一次就成功了,
写 hosts
访问一下,
啊,就是这样,能访问到,说明没啥子问题,说真的这个博客做的还是蛮不错的,而且看上去开源的原因也是作者被逼无奈,哈哈
说实话部署这个博客的流程和我部署我们公司项目的流程是一样的,先部署项目需要的基础服务,基础服务部署完成后就可以部署项目了,部署完项目之后将项目发布出来,当然之前是手写 nginx
配置文件,现在用的是 nginx-ingress
,这个东西真的好方便,自动关联 service
后端 pod
,缺点之前也提过,只能针对域名,不能针对端口。
刚刚扯到了基础服务,目前公司部署项目依赖的基础服务可不止 mysql&redis
,之前写 swarm
实战的时候提到过我们这里会用到的基础服务,之后如果没什么意外的话就开始做如何让这些基础服务跑在 k8s
上了。
其实还有一个最蛋疼的问题没有解决,就是每添加一个项目你就要创建一个任务、写一个资源文件、写一个 Jenkinsfile
,像我之前用的不是流水线,用的这个,
每次加一个项目我这里就要创建一个任务,然后写两个 playbook
,一个更新的一个回滚的,看一哈我之前写的,有 29
个项目写了 29
个发布的 playbook
再之后 jenkins
融合 swarm
就好很多了,写了一个可以复用的脚本去创建更新服务,定义了 N
多变量,也是通过 jenkins
传进去的,这个就轻松很多了,我不知道我之前的方式能不能把 N
个项目串到一起,但是通过 pipeline
可以,下面给你个思路。
我这里说的项目不是指的存在于 jenkins
中的 job
,说白了就是一个 pipeline
脚本可以将一套系统的所有项目都串起来,譬如之前 29
个项目组成一套系统,那时候创建了 29
个 job
,现在只需一个 pipeline
就可以搞定了,但是你需要写一个特别特别特别牛逼的 pipeline
脚本,配合 jenkins
的参数化构建去使用,通过 pipeline
去判断变量去做对应的操作,全部更新发布或是更新某些,这是一个很大的工程,涉及到的东西实在太多了,上面只是对 pipeline
脚本有一个最初步的认识,更高级的使用方法自行琢磨吧,记住 pipeline
写法能通过 jenkins
生成,思路放这里了,自行琢磨吧,下面聊聊 k8S
滚动更新
k8s
更新项目的时候默认使用的策略就是滚动更新 (rollingUpdate)
,他的逻辑就是每一次更新一个或多个服务,更新完成之后会加入到 endpoints
来接收请求,不断执行这个过程,直到集群中的所有旧版本替换成新版本,譬如我上面的那个 webstarter
有三个副本,在执行滚动更新的时候会先更新一个或两个,这一波没问题的话就开始更新下一波,直到全部更新到新版本,特点就是无感知平滑过渡,业务不受影响,默认策略,优先使用,下面看看它的原理是啥子。
其实滚动更新就是利用了再增加一个 ReplicaSet
去发布新版本去实现新旧的替换,这个 ReplicaSet
之前也提到过,在你创建 Deployment
的时候就会生成一个 ReplicaSet
,它是用来管理你 pod
副本数量的,也是一个控制器,说白了就是 Deployment
通过 ReplicaSet
去管理你的 pod
,也做一个版本的记录和滚动更新,这是 Deployment
引入 ReplicaSet
的目的。
在触发滚动更新的时候,Deployment
会再创建一个 ReplicaSet
去部署你的新版本,也就是这时候一个 Deployment
会有两个 ReplicaSet
,这个新创建的 ReplicaSet
也会去关联你的 service
,如果更新新版本莫得问题再去删除你的旧版本,全部更新完之后将新的 ReplicaSet
应用于当前的 Deployment
,保留旧的 ReplicaSet
用于回滚,这样就实现了滚动更新,言语是苍白的,实际操作看一哈。
我直接用 jenkins
去更新一下最开始的那个 java-demo
了,因为他的副本数是三个,看着会比较明显,等待更新完成,因为有健康检查了,更新会比较慢撒,更新时候有一个居然被 OOMKilled
了,看到这个错就是内存占用超了我的资源限制,所以就被杀了,所以在做资源限制的时候也要慎重,但这东西还不能不做,不做的话可能会给你的宿主机带来麻烦,现在已经更新完了。
先看一下 Deployment
的详情,会有事件显示,主要就是这里,
可以看到在触发滚动更新的时候新创建了一个名为 web-5c466f6896
的 replicasets
,设置它的副本数为 1
,这就是用来跑新版本的,这个正常启动之后下一步操作是把名为 web-c98f55d49
的 replicasets
副本调整成了 2
,这是旧的 replicasets
,所以这时候就是有两个旧版本一个新版本在跑,注意这不是同时进行的,是新的启动成功之后才会去停旧的,如果新的启动失败了,滚动更新就会暂停,不会影响旧的 replicasets
,最终结果就是新的 web-5c466f6896
副本数设置为 3
,旧的 web-c98f55d49
设置成 0
,下面看一下 ReplicaSet
就明白了。
可以看到旧的 replicasets
副本数已经被设置成 0
了,创建时间是 26h
以前,新的创建于 12m
之前,副本数被设置成 3
,这样就理解了撒,过程就是这样,回滚的话就是反向操作,先设置上一个版本的 replicasets
的副本数为 1
,一个旧的正常启动后缩减一个当前的 replicasets
,我回滚了一下,看图吧。
- [root@master-1 ~]# kubectl rollout undo deployment web
- deployment.extensions/web rolled back
更新回滚的原理就是这样的,其实你可以利用他这个原理去实现灰度发布,灰度发布是啥子呢?说白了就是先升级部分的服务,譬如你有 10
个 pod
,我先升级两个,这时候就集群中就又有 2
个新的 8
个旧的,会有一小部分用户去访问,如果新版本用户没反馈就开始扩大范围,我再升级 3
个,现在就一半一半了,如果用户还没啥子反馈就全部升级到新版本,这种灰度发布当前 K8s
是不支持的,变通一下就可以实现,思路如下。
需要使用两套 deployment
,上面提到了,deployment
会通过 replicasets
实现新旧版本的更新,灰度发布实现的原理和他是样的,譬如现在我要灰度发布,现在已经有一个 deployment
了,我再创建一个部署新版应用的 deployment
,当然这个 deployment
的名字不要和之前一样撒,相同的地方是这两个的 deployment
所关联的 service
是同一个,这样当有新的 pod
启动后就会和旧的 pod
并行提供服务,这个是重点,结合上面提到的东西,我先把新的 deployment
所关联的 replicasets
进行扩展,譬如目前老版本有 10
个 pod
,我先启动两个新的,这两个新的启动完成之后我再去缩容旧的 pod
的数量,譬如我缩容到八个,这时候就有两个新的八个旧的并行提供服务了,反馈没问题之后再进行同样的操作,直到将新的扩容到 10
,旧的缩容成 0
,懂我啥意思了吧,这样就实现了灰度发布。
这样做的好处是影响范围可控,灰度发布是目前比较主流的方案,想要实现这个方案有一个很大的难点,就是去控制每一波升级缩容的数量,要扩容多少新的,缩减多少旧的,就白了就是你要有效控制新的扩容旧的缩容,最简单的办法就是手动去 scale
,哈哈,最好的实现方法还是在程序层面去控制,思路就是这样,下面来看看滚动更新的策略。
这个策略就是用来定义滚动更新每次要更新多少个 pod
,我们之前没有在 YAML
文件里定义过,所以用的就是默认的策略,现在看一下。
- [root@master-1 ~]# kubectl get deployments.apps web -o yaml
- strategy:
- rollingUpdate:
- maxSurge: 25%
- maxUnavailable: 25%
- type: RollingUpdate
maxSurge
用来设置要新启动多少个 pod
来作为新副本,目前的配置是当前 pod
总数量的百分之二十五,maxUnavailable
是设置最大不可用的副本,设置的也是当前 pod
总数量的百分之二十五,也就是说譬如有 12 pod
,在更新的时候我先启动 3
个新的,启动三个没问题之后关掉三个旧的,再唠叨一遍,健康检查生产环境必加,不加健康检查再牛逼的更新策略都无法帮你平滑更新。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。