当前位置:   article > 正文

使用 jenkins 构建 CI/CD 平台_ci cd jenkins

ci cd jenkins

CI/CD 概述

大概了解一下 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 装了吧。

 

部署 Harbor 镜像仓库

这个是用来存镜像的,我直接在 kubeadm 的 node 节点上部署了,一会把 git 也仍在这里吧,懒得去创建虚拟机了,Harbor 官方地址,目前有两种安装包,分别是在线安装和离线安装,我用的在线安装,因为离线安装包有点大,还需要 docker-compose 的支持,

  1. [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
  2. [root@kubeadm-node /]# wget https://storage.googleapis.com/harbor-releases/release-1.8.0/harbor-online-installer-v1.8.0.tgz
  3. [root@kubeadm-node /]# tar zxf harbor-online-installer-v1.8.0.tgz
  4. [root@kubeadm-node /]# cd harbor/
  5. [root@kubeadm-node /harbor]# ls
  6. harbor.yml install.sh LICENSE prepare

需要先初始化一下,

[root@kubeadm-node /harbor]# ./prepare

会下载一个镜像,目录里面多东西了,编辑一下 harbor.yml 改点东西,三处。

  1. hostname: 192.168.1.248 #访问harbor的域名,没域名写IP
  2. data_volume: /harbor #数据目录,改不改看你撒
  3. harbor_admin_password: Harbor12345 # 默认admin登陆密码,改不改随你

改完之后就可以安装了,直接执行 install.sh 就可以了,会下载 N 个镜像,下载完成之后会自行启动。

  1. [root@kubeadm-node /harbor]# ./install.sh
  2. [root@kubeadm-node /harbor]# docker-compose ps

 

 

访问一下,

 

 

部署还是比较简单的,随便推个镜像试试。

 

 

这个就比较烦人了,我特么实在是不想重启 docker,算了,估计一会就不用了,用云提供商的吧,下面部署一下 Git

 

部署 Git 仓库

git 是目前比较主流的,先把这个包装一下吧,如果你用 SVN 自行安装配置一下吧,不是很麻烦,

[root@kubeadm-node ~]# yum -y install git

访问仓库的方式这里使用 ssh 方式来访问了,随便创建一个用户,然后设置一个密码。

  1. [root@kubeadm-node ~]# useradd rj-bai
  2. [root@kubeadm-node ~]# echo Sowhat? | passwd --stdin rj-bai

切换到刚刚创建的用户,创建一个目录,初始化一下作为代码仓库。

  1. [rj-bai@kubeadm-node ~]$ mkdir rj-bai.git
  2. [rj-bai@kubeadm-node ~]$ cd rj-bai.git/
  3. [rj-bai@kubeadm-node ~/rj-bai.git]$ git --bare init
  4. Initialized empty Git repository in /home/rj-bai/rj-bai.git/
  5. [rj-bai@kubeadm-node ~/rj-bai.git]$ ls
  6. branches config description HEAD hooks info objects refs

这就创建完了,具体怎么拉取这个代码,如下,在 master 上执行了,直接 git clone 就行了。

  1. [root@master-1 ~]# cd /tmp/ && ls
  2. metrics-server systemd-private-5afd87d103b74339a4fac02fdb472124-chronyd.service-FVUqYN
  3. [root@master-1 /tmp]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
  4. Cloning into 'rj-bai'...
  5. Warning: Permanently added '192.168.1.248' (ECDSA) to the list of known hosts.
  6. rj-bai@192.168.1.248's password:
  7. warning: You appear to have cloned an empty repository.
  8. [root@master-1 /tmp]# ls rj-bai/

提示目录时空的,随便创建一个文件提交一下

  1. [root@master-1 /tmp/rj-bai]# cat <<OEF >index.html
  2. > testing
  3. > OEF
  4. [root@master-1 /tmp/rj-bai]# cat index.html
  5. testing
  6. [root@master-1 /tmp/rj-bai]# git add .
  7. [root@master-1 /tmp/rj-bai]# git config --global user.email "your@email"
  8. [root@master-1 /tmp/rj-bai]# git config --global user.name "rj-bai"
  9. [root@master-1 /tmp/rj-bai]# git commit -m 'index'
  10. [master (root-commit) 8f27bc9] index
  11. 1 file changed, 1 insertion(+)
  12. create mode 100644 index.html
  13. [root@master-1 /tmp/rj-bai]# git push origin master
  14. rj-bai@192.168.1.248's password:
  15. Counting objects: 3, done.
  16. Writing objects: 100% (3/3), 215 bytes | 0 bytes/s, done.
  17. Total 3 (delta 0), reused 0 (delta 0)
  18. To rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
  19. * [new branch] master -> master

提交到了 master 分支,验证一下,这个目录删了,重新拉去一下,能拉到就说明没啥子问题。

  1. [root@master-1 /tmp]# rm -rf rj-bai/
  2. [root@master-1 /tmp]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
  3. Cloning into 'rj-bai'...
  4. rj-bai@192.168.1.248's password:
  5. remote: Counting objects: 3, done.
  6. remote: Total 3 (delta 0), reused 0 (delta 0)
  7. Receiving objects: 100% (3/3), done.
  8. [root@master-1 /tmp]# ls rj-bai/
  9. index.html

嗯,就是这样,莫得问题,还有就是每次都要输入密码,配一个秘钥就好了,我现在有了,我直接 copy 去了。

  1. [root@master-1 ~]# ssh-copy-id rj-bai@192.168.1.248
  2. [root@master-1 ~]# git clone rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git
  3. [root@master-1 ~]# ls rj-bai/
  4. index.html

这样就可以了,仓库和 Git 都准好了,暂时先不动他了,下面开始在 k8s 集群中部署 jenkins

 

在 K8S 中部署 jenkins

jenkins 官网,既然是在 k8s 中部署就直接使用 docker 方式了,github 地址,官方说明有一个目录需要做持久化,也就是 /var/jenkins_homejenkins 所有的数据都是存在这个目录下面的,jenkins 还需要一个唯一的网络标识,也就是说需要有状态的去部署 jenkins 了,这里就不多 BB 了,直接用 StatefulSet 方式去部署,依旧使用 NFS 动态供给作为存储,NFS 的部署方法之前写过了,部署 jenkins 的 YAML 文件是使用官方的模板,现在直接 git clone 下来。

  1. [root@master-1 ~]# mkdir jenkins
  2. [root@master-1 ~]# cd jenkins/
  3. [root@master-1 ~/jenkins]# git clone https://github.com/jenkinsci/kubernetes-plugin.git

克隆完之后进入到这个目录。

  1. [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# pwd
  2. /root/jenkins/kubernetes-plugin/src/main/kubernetes
  3. [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# ls
  4. 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

 

部署 nginx-ingress

还是直接用官方的模板,先 git clone 下来,进到这个目录。

  1. [root@master-1 ~/demo/nginx-ingress]# git clone https://github.com/nginxinc/kubernetes-ingress.git
  2. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# pwd
  3. /root/demo/nginx-ingress/kubernetes-ingress/deployments

按着官方的步骤这样部署,当然有些暂时用不到的步骤我直接省略了,官方文档

1. 创建命名空间和服务账户。

  1. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/ns-and-sa.yaml
  2. namespace/nginx-ingress created
  3. serviceaccount/nginx-ingress created

2. 使用 TLS 证书和 NGINX 中默认服务器的密钥创建密钥

说白了就是给 nginx 一个默认的 TLS 证书,这个证书也是他们自签的,不受信任。

  1. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/default-server-secret.yaml
  2. secret/default-server-secret created

3. 创建用于自定义 nginx 的配置文件

  1. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f common/nginx-config.yaml
  2. configmap/nginx-config created

4. 配置 RBAC

  1. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f rbac/rbac.yaml
  2. clusterrole.rbac.authorization.k8s.io/nginx-ingress created
  3. clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress created

5. 部署 ingress 控制器

这里的话用两种方法,分别为 deployment&&DaemonSet,我这里直接用 DaemonSet 方式去部署了,具体为什么,官方说明,翻译内容如下。

如果您创建了 DaemonSet,则 Ingress 控制器容器的端口 80 和 443 将映射到运行容器的节点的相同端口。要访问 Ingress 控制器,请使用这些端口以及运行 Ingress 控制器的群集中任何节点的 IP 地址。

省得再去创建 Services 了,看了一下部署控制器的文件,args 里面启用了两个,还有 6 个没启用,我翻译了一下,结果如下。

 

 

好像暂时没啥子能用到的,查看状态的可以考虑开启,我这里暂时就不开启了,直接创建了。

  1. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl apply -f daemon-set/nginx-ingress.yaml
  2. daemonset.extensions/nginx-ingress created
  3. [root@master-1 ~/demo/nginx-ingress/kubernetes-ingress/deployments]# kubectl -n nginx-ingress get pod

 

 

这样就可以了撒,部署完了,所有 Node 节点都有运行一个 nginx-ingress 控制器,除了两个 master 节点,因为我没允许 master 运行 PodIngress 部署完了,现在可以部署 jenkins 了。

 

部署 jenkins

部署文件之前已经修改过了,所以直接创建就完了。

  1. [root@master-1 ~/jenkins/kubernetes-plugin/src/main/kubernetes]# kubectl apply -f .
  2. [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 动态创建代理

现在需要 jenkins 和 k8s 集成,需要用到一个名为 kubernetes 的插件,这个插件是用来动态创建代理的,也是自动创建 slave 端,slave 就是你添加的工作节点,这里不需要你手动添加了,在有任务的时候 jenkins 会自动创建 pod 作为自己的 slave 节点,master-slave 架构解决了 master 单点负载的问题,使其演变成了一个分布式的架构,其实 slave 节点就是运行了一个 jar 包,我之前也搞过这个,这个 jar 包启动后会去连接 master 接收任务。

目前 jenkins 是 k8s 中的一个 pod,所以他动态创建的 slave 也是集群中的 podmaster 和 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 镜像。

 

构建 jenkins-slave 镜像

做一个验证,都配置完了,到底能不能用,直接用流水线了,目前还没装这个插件,所以先装一下,插件名就是 Pipeline

 

 

这次装的比较多,让他装着吧,装完后自动重启,一会会写一个 Pipeline 脚本,以测试 jenkins 能不能在集群中动态创建 slave-pod,这个是必需步骤,否则无法继续下去,这个 slave-pod 的镜像需要自己做一个,下面开始制作这个镜像,其实是有默认的镜像,但是不用他默认的,对于我来说功能不全,这个 slave 镜像会完成代码拉取、单元测试、代码编译、构建镜像、推送镜像的步骤,所以现在做的 slave 镜像要有这些功能,下面开始编写 Dockerfile

 

编写 Dockerfile

上文提到了这个镜像需要完成代码拉取,单元测试 (和现在没啥子关系),代码编译,镜像构建以及推送镜像,所以你的镜像需要支持这些功能才可以,所以 Dockerfile 如下,按着自己的情况更改吧,官方参考地址

 

  1. FROM centos:latest
  2. RUN yum -y install java-1.8.0-openjdk maven curl git subversion libtool-ltdl-devel && \
  3. yum clean all && \
  4. rm -rf /var/cache/yum/* && \
  5. mkdir -p /usr/share/jenkins
  6. COPY slave.jar /usr/share/jenkins/slave.jar
  7. COPY jenkins-slave /usr/bin/jenkins-slave
  8. COPY settings.xml /etc/maven/settings.xml
  9. RUN chmod +x /usr/bin/jenkins-slave
  10. ENTRYPOINT ["jenkins-slave"]

settings.xml 是 maven 的配置文件,这个文件怎么写建议咨询一下开发人员,我还是用我们私服的,用默认的也可以,可能下载依赖包会比较慢,默认地址是国外的,jenkins-slave 也是在参考地址直接 copy 过来的,也不贴了,slave.jar 自行下载吧,地址就是 $JENKINS_URL/jnlpJars/slave.jar,我这里也不传了,都准备好了直接 build 就完了。

  1. [root@master-1 /data/docker/jenkins-slave-jdk]# ls
  2. Dockerfile jenkins-slave settings.xml slave.jar
  3. [root@master-1 /data/docker/jenkins-slave-jdk]# docker build -t jenkins-slave-jdk:1.8 .

 

 

这样就构建完了,然后把这个镜像推送到镜像仓库,我不用刚刚搭建的 Harhor,我直接推到 docker hub 去了,就是比较慢

  1. [root@master-1 /data/docker/jenkins-slave-jdk]# docker tag jenkins-slave-jdk:1.8 bairuijie/jenkins-slave-jdk:1.8
  2. [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 来完成,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 需要 mavenmaven 依赖 jdk,不用看了,目前 jenkins 的 POD 上虽有 java 但没有 maven,在上文的描述中拉取编译代码也是由 slave 去完成的,所以现在要启动 slave 了,既然是动态创建,就需要用到 pod 模板了。

 

启动测试 slave 节点

直接在 pipeline 脚本中定义吧,改完的 pipeline 脚本内容如下。

  1. podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
  2. containerTemplate(
  3. name: 'jnlp',
  4. image: "registry.cn-beijing.aliyuncs.com/rj-bai/jenkins-slave-jdk:1.8"
  5. ),
  6. ],
  7. volumes: [
  8. hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  9. hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
  10. ],
  11. )
  12. {
  13. node ("jenkins-slave"){
  14. def mvnHome
  15. stage('拉取') {
  16. git credentialsId: 'efb4268b-f758-4b58-9dcd-d966faf8360e', url: 'rj-bai@192.168.1.248:/home/rj-bai/rj-bai.git'
  17. sh 'ls -l'
  18. }
  19. stage('建构') {
  20. }
  21. stage('部署') {
  22. }
  23. }
  24. }

上面的那一段就是定义 slave 节点的声明,使用 jnlp 协议,镜像我传到了阿里云的仓库,这个是公开的,一会在构建的时候他回去拉这个镜像作为 slave 启动,我直接也把 docker 挂进去了,方便构建推送镜像,这样配置之后构建镜像就不是由 master 来完成的了,而是由动态创建的 slave 来完成的,测试一下,保存,然后构建,动态查看 pod 状态。

 

 

这里构建成功了,看一下 k8s 集群中 pod 的变化。

 

 

这就是动态创建 slave 了,在需要的时候创建一个,slave 完成任务后就会被销毁,这一块没啥子问题,下面准备一下要编译的源代码。

 

准备 java 源代码

既然是编译,你就需要有 java 的源码了,建议在这里生成一个,目前只要有简单的 web 访问就够了,所以选这个,编译成功后会有一个 jar 文件,jdk 版本 1.8,这样就可以了。

 

 

选完点击绿色按钮你会下载一个名为 demo.zip 的压缩包,这就是源码了,然后在 git 上创建一个仓库,把代码提上去。

  1. [rj-bai@kubeadm-node ~]$ mkdir webstarter.git
  2. [rj-bai@kubeadm-node ~]$ cd webstarter.git/
  3. [rj-bai@kubeadm-node ~/webstarter.git]$ git --bare init
  4. Initialized empty Git repository in /home/rj-bai/webstarter.git/

仓库这里算是创建完了,然后去 master 提交代码。

  1. [root@master-1 ~]# git clone rj-bai@192.168.1.248:/home/rj-bai/webstarter.git
  2. [root@master-1 ~]# cd webstarter/
  3. [root@master-1 ~/webstarter]# unzip demo.zip ## 文件自行上传
  4. [root@master-1 ~/webstarter]# mv demo/* .
  5. [root@master-1 ~/webstarter]# rm -rf demo*
  6. [root@master-1 ~/webstarter]# git add .
  7. [root@master-1 ~/webstarter]# git commit -m 'webstarter'
  8. [root@master-1 ~/webstarter]# git push origin master

 

 

这样就可以了撒,其实 pipeline 的配置也可以写到一个名为 Jenkinsfile 的文件里,这个文件需要放在代码的根目录,这个后面会涉及到,下面开始配置编译代码的部分。

 

编译代码构建和推送镜像

编译代码这里需要 maven 去编译,构建镜像的话就是将项目包传到镜像里,一会会用 openjdk 的镜像,推送镜像就是将刚刚构建好的镜像推送到镜像仓库,既然涉及到了推送镜像就一定会给镜像打标签,具体这个标签怎么打,还是像之前那样,项目名 + 构建次数,所以脚本暂时写成这样,定义了 N 多变量。

  1. // 镜像仓库地址
  2. def registry = "registry.cn-beijing.aliyuncs.com"
  3. // 项目&镜像配置信息
  4. def namespace = "rj-bai"
  5. def app_name = "webstarter"
  6. def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
  7. def git_address = "rj-bai@192.168.1.248:/home/rj-bai/webstarter.git"
  8. // 认证信息ID
  9. def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
  10. def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
  11. podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
  12. containerTemplate(
  13. name: 'jnlp',
  14. image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
  15. ),
  16. ],
  17. volumes: [
  18. hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  19. hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
  20. ],
  21. )
  22. {
  23. node("jenkins-slave"){
  24. stage('拉取代码'){
  25. checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
  26. }
  27. stage('代码编译'){
  28. sh "mvn clean package -Dmaven.test.skip=true"
  29. }
  30. stage('构建镜像'){
  31. withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  32. sh label: '', script: ''' echo \'
  33. FROM openjdk:8
  34. ADD target/*.jar /
  35. ADD entrypoint.sh /
  36. RUN chmod +x /entrypoint.sh
  37. CMD ["/bin/bash","/entrypoint.sh"]
  38. \' > Dockerfile
  39. echo \'
  40. #!/bin/bash
  41. app=`ls /*.jar`
  42. java -jar $app
  43. \' > entrypoint.sh'''
  44. sh """
  45. docker build -t ${image_name} .
  46. docker login -u ${username} -p \"${password}\" ${registry}
  47. docker push ${image_name}
  48. """
  49. }
  50. }
  51. }
  52. }

你就当成 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 阶段。

 

jenkins 在 k8s 中持续部署

上面镜像已经准备好了,现在该实现自动部署到 k8s 中了,要想实现这个还需要一个插件,名为 kubernetes continuous deploy,用于将资源部署到 k8s 中,他支持绝大部分的资源类型,像是什么 deployment&service,自行安装吧,简单看一下这个插件的介绍。

官方地址,主要看一下这一段,这是配置在 pipeline 中使用的写法

 

 

kubeconfigId 这里是需要指定一个 kubeconfig 的 ID,这个东西就是用于连接 k8s 的一个配置文件,所以我们要把这个文件内容保存到 jenkins 中作为凭据,然后去引用凭据的 ID

config 这里用来指定资源文件,也就是你部署服务的 YAML 文件。

  1. secretNamespace: '<secret-namespace>',
  2. secretName: '<secret-name>',

这块是用来指定 secret 的,有两个是必须的,一个是 kubeconfig 文件,再就是资源文件,这个插件的 pipeline 写法也是可以生成的,所以还是用之前的工具来生成一下。

 

 

现在开始让你添加 kubeconfig 的文件了,添加吧,选择这个,

 

 

这个文件具体要怎么获取,如果你的集群是 kubeadm 安装的获取很简单,就是这个文件。

[root@rj-bai ~]# cat .kube/config

把这个文件的内容复制出来保存到 jenkins 里面就可以了,如果你是二进制部署的集群,就要手动去生成这个文件了,这个文件默认是没有的,具体怎么生成这个文件之前写过,这里就不贴了,我使用的集群也是二进制方式部署的,我直接把之前生成的文件拿过来就直接用了,然后那个 config Files 就是指定资源文件了,这个文件就是用来部署我们的项目的,emmmm,写一个吧,我写的如下。

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: web
  5. spec:
  6. replicas: 3
  7. selector:
  8. matchLabels:
  9. app: web-starter
  10. template:
  11. metadata:
  12. labels:
  13. app: web-starter
  14. spec:
  15. imagePullSecrets:
  16. - name: $SECRET_NAME
  17. containers:
  18. - name: web-starter
  19. image: $IMAGE_NAME
  20. ports:
  21. - containerPort: 8080
  22. name: web
  23. livenessProbe:
  24. httpGet:
  25. path: /favicon.ico
  26. port: 8080
  27. initialDelaySeconds: 30
  28. timeoutSeconds: 5
  29. failureThreshold: 3
  30. readinessProbe:
  31. httpGet:
  32. path: /favicon.ico
  33. port: 8080
  34. initialDelaySeconds: 30
  35. timeoutSeconds: 5
  36. failureThreshold: 3
  37. resources:
  38. requests:
  39. memory: "64Mi"
  40. cpu: "250m"
  41. limits:
  42. memory: "128Mi"
  43. cpu: "500m"
  44. ---
  45. apiVersion: v1
  46. kind: Service
  47. metadata:
  48. name: web
  49. spec:
  50. type: NodePort
  51. selector:
  52. app: web-starter
  53. ports:
  54. - protocol: TCP
  55. port: 80
  56. targetPort: 8080
  57. name: web
  58. ---
  59. apiVersion: extensions/v1beta1
  60. kind: Ingress
  61. metadata:
  62. name: web
  63. spec:
  64. rules:
  65. - host: webstarter.rj-bai.com
  66. http:
  67. paths:
  68. - path: /
  69. backend:
  70. serviceName: web
  71. servicePort: 80

应该都能看懂,我部署了 nginx-ingress-controller,所以就直接以 ingress 方式发布出去了,如果你没有部署 ingress 控制器就直接用 NodePort 吧,看一哈这个文件中有两个变量,一个是用来存放认证登陆信息的 secrets 名字,再一个就是镜像地址,一会使用 sed 去替换成相对应的值,暂时就定义这两个变量,其实还有很多可以定义,只要是经常变动的,像是什么健康检查端口绑定域名也可用变量,用变量的目的就是复用,自行琢磨吧,这个文件需要存在你 git 版本代码仓库中,自行上传提交一下吧,我的名为 deploy-webstarter.yaml

所以,最终的配置文件 pipeline 脚本如下。

  1. // 镜像仓库地址
  2. def registry = "registry.cn-beijing.aliyuncs.com"
  3. // 项目&镜像配置信息
  4. def namespace = "rj-bai"
  5. def app_name = "webstarter"
  6. def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
  7. def git_address = "rj-bai@192.168.1.248:/home/rj-bai/webstarter.git"
  8. // 认证信息ID
  9. def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
  10. def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
  11. def secret_name = "registry-secret"
  12. def k8s_auth = "9823945c-07f6-495c-bc48-a47d8f43536c"
  13. podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
  14. containerTemplate(
  15. name: 'jnlp',
  16. image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
  17. ),
  18. ],
  19. volumes: [
  20. hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  21. hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
  22. ],
  23. )
  24. {
  25. node("jenkins-slave"){
  26. stage('拉取代码'){
  27. checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
  28. }
  29. stage('代码编译'){
  30. sh "mvn clean package -Dmaven.test.skip=true"
  31. }
  32. stage('构建镜像'){
  33. withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  34. sh label: '', script: ''' echo \'
  35. FROM openjdk:8
  36. ADD target/*.jar /
  37. ADD entrypoint.sh /
  38. RUN chmod +x /entrypoint.sh
  39. CMD ["/bin/bash","/entrypoint.sh"]
  40. \' > Dockerfile
  41. echo \'
  42. #!/bin/bash
  43. app=`ls /*.jar`
  44. java -jar $app
  45. \' > entrypoint.sh'''
  46. sh """
  47. docker build -t ${image_name} .
  48. docker login -u ${username} -p \"${password}\" ${registry}
  49. docker push ${image_name}
  50. """
  51. }
  52. }
  53. stage('部署到K8S'){
  54. sh """
  55. sed -i 's#\$IMAGE_NAME#${image_name}#' deploy-webstarter.yaml
  56. sed -i 's#\$SECRET_NAME#${secret_name}#' deploy-webstarter.yaml
  57. """
  58. kubernetesDeploy configs: 'deploy-webstarter.yaml', kubeconfigId: "${k8s_auth}"
  59. }
  60. }
  61. }

增加了两个变量,一个是存 kubeconfig 的 ID,一个是 secret 的名字,这个是干嘛的不用多说了,如果没有自行创建吧,使用 sed 替换了镜像地址和 secret 的名字,最后 kubernetesDeploy 配置的那里,现在就用到的就这两个,没用到的全部去掉了,开始跑吧。

 

最终测试

开始构建,然后动态查看 pod 的状态,先看页面,显示成功。

 

 

再看 pod 的状态,都已经正常启动了,

 

 

然后手写 hosts 访问一下,

 

 

莫得问题,就是这种效果,现在也完成了持续部署,生成的 demo 就是一个简单的 web,没有任何的页面,只有默认的 404, 健康检查那里我检查的还是 favicon.ico,反正是起来了,访问也没问题,过。

 

使用 Jenkinsfile

其实这个 pipeline 脚本的内容也可以放在项目的根目录,也就是和 deploy-webstarter.yaml 同级,不需要写在 jenkins 里,这样的话比较方便管理,再加项目的时候你只需要在 jenkins 上创建 job 直接引用仓库地址就可以了,现在就要达到这个目的,部署一个东西撒,一个名为 DimpleBlog 的 java 博客,感兴趣的去 github 上搜一下吧,编译好了也是一个 jar 包,拿到源码之后还是新建仓库,master 拉一下把代码将源码传到仓库,先不要提交到 git 中,需要改点东西。

刚刚提到了这是一个博客程序,要他运行起来需要 mysql&&redis,他的 sql 文件是存在源码的 sql 文件夹里,我集群中刚好有一个 mysql,也是之前创建的,创建方式我就不贴了,之前贴过,我看 sql 里用的是 test 表,我手动导入进去了,现在还莫得 redis,创建一个 redis 出来吧,使用 StatefulSet 创建,和创建 mysql 的方式一致

  1. [root@master-1 ~/demo]# cat redis.yaml
  2. apiVersion: v1
  3. kind: Service
  4. metadata:
  5. name: redis
  6. spec:
  7. ports:
  8. - port: 6379
  9. name: redis
  10. clusterIP: None
  11. selector:
  12. app: redis
  13. ---
  14. apiVersion: apps/v1beta1
  15. kind: StatefulSet
  16. metadata:
  17. name: redis
  18. spec:
  19. updateStrategy:
  20. type: RollingUpdate
  21. serviceName: "redis"
  22. template:
  23. metadata:
  24. labels:
  25. app: redis
  26. spec:
  27. containers:
  28. - name: redis
  29. image: redis:latest
  30. ports:
  31. - containerPort: 6379
  32. name: redis
  33. volumeMounts:
  34. - mountPath: "/data"
  35. name: redis-data
  36. livenessProbe:
  37. tcpSocket:
  38. port: 6379
  39. initialDelaySeconds: 10
  40. periodSeconds: 3
  41. failureThreshold: 3
  42. volumes:
  43. - name: redis-data
  44. persistentVolumeClaim:
  45. claimName: redis-data
  46. volumeClaimTemplates:
  47. - metadata:
  48. name: redis-data
  49. spec:
  50. accessModes: [ "ReadWriteOnce" ]
  51. resources:
  52. requests:
  53. storage: 1Gi
  54. [root@master-1 ~/demo]# kubectl apply -f redis.yaml
  55. service/redis created
  56. statefulset.apps/redis created
  57. [root@master-1 ~/demo]# kubectl get pod redis-0
  58. NAME READY STATUS RESTARTS AGE
  59. redis-0 1/1 Running 0 3m10s

已经启动了,然后去改一下这个项目连接数据库的配置文件,这个位置,

  1. [root@master-1 ~/DimpleBlog]# vim src/main/resources/application-druid.yml
  2. # 主库数据源
  3. master:
  4. url: jdbc:mysql://mysql:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
  5. username: root
  6. password: Sowhat?

这是数据库的,还要改一下 redis 的,

  1. [root@master-1 ~/DimpleBlog]# vim src/main/resources/application.yml
  2. redis:
  3. host: redis
  4. port: 6379
  5. database: 0

这样就可以了撒,还需要创建资源文件和 jenkinsfile,先把资源文件写了吧,按着之前的改改就行了,我改成这样。

  1. [root@master-1 ~/DimpleBlog]# cat deploy-dimpleblog.yaml
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: web-dimpleblog
  6. spec:
  7. replicas: 1
  8. selector:
  9. matchLabels:
  10. app: dimpleblog
  11. template:
  12. metadata:
  13. labels:
  14. app: dimpleblog
  15. spec:
  16. imagePullSecrets:
  17. - name: $SECRET_NAME
  18. containers:
  19. - name: dimpleblog
  20. image: $IMAGE_NAME
  21. ports:
  22. - containerPort: 80
  23. name: web-dimpleblog
  24. livenessProbe:
  25. httpGet:
  26. path: /
  27. port: 80
  28. initialDelaySeconds: 60
  29. timeoutSeconds: 10
  30. failureThreshold: 5
  31. readinessProbe:
  32. httpGet:
  33. path: /
  34. port: 80
  35. initialDelaySeconds: 60
  36. timeoutSeconds: 10
  37. failureThreshold: 5
  38. resources:
  39. requests:
  40. memory: "512Mi"
  41. cpu: "0.5"
  42. limits:
  43. memory: "1024Mi"
  44. cpu: "1"
  45. ---
  46. apiVersion: v1
  47. kind: Service
  48. metadata:
  49. name: web-dimpleblog
  50. spec:
  51. type: NodePort
  52. selector:
  53. app: dimpleblog
  54. ports:
  55. - protocol: TCP
  56. port: 80
  57. targetPort: 80
  58. name: web-dimpleblog
  59. ---
  60. apiVersion: extensions/v1beta1
  61. kind: Ingress
  62. metadata:
  63. name: web-dimpleblog
  64. spec:
  65. rules:
  66. - host: dimpleblog.rj-bai.com
  67. http:
  68. paths:
  69. - path: /
  70. backend:
  71. serviceName: web-dimpleblog
  72. servicePort: 80

这次健康检查时间调长了,资源文件有了,然后是 jenkinsfile,放在和资源文件目录同级的地方,

  1. [root@master-1 ~/DimpleBlog]# cat Jenkinsfile
  2. // 镜像仓库地址
  3. def registry = "registry.cn-beijing.aliyuncs.com"
  4. // 项目&镜像配置信息
  5. def namespace = "rj-bai"
  6. def app_name = "dimpleblog"
  7. def image_name = "${registry}/${namespace}/${app_name}:${BUILD_NUMBER}"
  8. def git_address = "rj-bai@192.168.1.248:/home/rj-bai/DimpleBlog.git"
  9. // 认证信息ID
  10. def docker_registry_auth = "400878cf-e740-459f-b8bf-ad2c749b833e"
  11. def git_auth = "efb4268b-f758-4b58-9dcd-d966faf8360e"
  12. def secret_name = "registry-secret"
  13. def k8s_auth = "9823945c-07f6-495c-bc48-a47d8f43536c"
  14. podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', containers: [
  15. containerTemplate(
  16. name: 'jnlp',
  17. image: "${registry}/${namespace}/jenkins-slave-jdk:1.8"
  18. ),
  19. ],
  20. volumes: [
  21. hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  22. hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
  23. ],
  24. )
  25. {
  26. node("jenkins-slave"){
  27. stage('拉取代码'){
  28. checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
  29. }
  30. stage('代码编译'){
  31. sh "mvn clean package -Dmaven.test.skip=true"
  32. }
  33. stage('构建镜像'){
  34. withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
  35. sh label: '', script: ''' echo \'
  36. FROM openjdk:8
  37. ADD target/*.jar /
  38. ADD entrypoint.sh /
  39. RUN chmod +x /entrypoint.sh
  40. CMD ["/bin/bash","/entrypoint.sh"]
  41. \' > Dockerfile
  42. echo \'
  43. #!/bin/bash
  44. app=`ls /*.jar`
  45. java -jar $app
  46. \' > entrypoint.sh'''
  47. sh """
  48. docker build -t ${image_name} .
  49. docker login -u ${username} -p \"${password}\" ${registry}
  50. docker push ${image_name}
  51. """
  52. }
  53. }
  54. stage('部署到K8S'){
  55. sh """
  56. sed -i 's#\$IMAGE_NAME#${image_name}#' deploy-dimpleblog.yaml
  57. sed -i 's#\$SECRET_NAME#${secret_name}#' deploy-dimpleblog.yaml
  58. """
  59. kubernetesDeploy configs: 'deploy-dimpleblog.yaml', kubeconfigId: "${k8s_auth}"
  60. }
  61. }
  62. }

也是按之前的改了改,这样就够了,然后提交代码,

  1. [root@master-1 ~/DimpleBlog]# git add -A
  2. [root@master-1 ~/DimpleBlog]# git commit -m 'master'
  3. [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 滚动更新

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,我回滚了一下,看图吧。

  1. [root@master-1 ~]# kubectl rollout undo deployment web
  2. 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 文件里定义过,所以用的就是默认的策略,现在看一下。

  1. [root@master-1 ~]# kubectl get deployments.apps web -o yaml
  2. strategy:
  3. rollingUpdate:
  4. maxSurge: 25%
  5. maxUnavailable: 25%
  6. type: RollingUpdate

maxSurge 用来设置要新启动多少个 pod 来作为新副本,目前的配置是当前 pod 总数量的百分之二十五,maxUnavailable 是设置最大不可用的副本,设置的也是当前 pod 总数量的百分之二十五,也就是说譬如有 12 pod,在更新的时候我先启动 3 个新的,启动三个没问题之后关掉三个旧的,再唠叨一遍,健康检查生产环境必加,不加健康检查再牛逼的更新策略都无法帮你平滑更新。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/678371
推荐阅读
相关标签
  

闽ICP备14008679号