天天看点

使用 jenkins 构建 CI/CD 平台

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 构建 CI/CD 平台

​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​

​ 的支持,

[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      
使用 jenkins 构建 CI/CD 平台

访问一下,

使用 jenkins 构建 CI/CD 平台

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

使用 jenkins 构建 CI/CD 平台

这个就比较烦人了,我特么实在是不想重启 ​

​docker​

​​,算了,估计一会就不用了,用云提供商的吧,下面部署一下 ​

​Git​

​。

部署 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 [email protected]:/home/rj-bai/rj-bai.git
Cloning into 'rj-bai'...
Warning: Permanently added '192.168.1.248' (ECDSA) to the list of known hosts.
[email protected]'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
[email protected]'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 [email protected]:/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 [email protected]:/home/rj-bai/rj-bai.git
Cloning into 'rj-bai'...
[email protected]'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 [email protected]
[root@master-1 ~]# git clone [email protected]:/home/rj-bai/rj-bai.git
[root@master-1 ~]# ls rj-bai/
index.html      

这样就可以了,仓库和 ​

​Git​

​​ 都准好了,暂时先不动他了,下面开始在 ​

​k8s​

​​ 集群中部署 ​

​jenkins​

​。

在 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​

​。

使用 jenkins 构建 CI/CD 平台

这里使用了 ​

​PV​

​​ 的模板,是需要你提供 ​

​PV​

​​ 自动供给的支持的,默认的类型是 ​

​anything​

​​,目前我只有一个 ​

​NFS​

​ 的,也就是这个。

使用 jenkins 构建 CI/CD 平台

所以说一会创建的时候它默认就会用这个了,如果你有两种类型的存储且需要指定就按正常流程走指定就完了,申请磁盘空间这里我把它改成 ​

​5G​

​​ 了,​

​1G​

​​ 实在是有点小,还有资源限制那里,限制最小 ​

​0.5​

​​ 核 ​

​CPU&0.5G​

​​ 内存,最大 ​

​1​

​​ 核 ​

​1G​

​​ 内存,说实话有点小,最起码内存调大一点,我这里最大限制都调成 ​

​2​

​ 了。

又看了一眼 ​

​Service​

​​ 这里,访问方式用的是 ​

​Ingress​

​​,你可以用 ​

​NodePort​

​​ 方式去访问,我这里用 ​

​Ingress​

​​ 方式去做了,好久没搞过 ​

​nginx-ingress​

​​ 了,先把 ​

​tls​

​​ 方法去了,​

​hosts​

​ 随便改一下,我改完的如下。

使用 jenkins 构建 CI/CD 平台

如果你不想用 ​

​Ingress​

​​ 方式就删除掉上图光标的位置的注释,​

​type​

​​ 改为 ​

​NodePort​

​​ 且略过部署下面部署 ​

​nginx-ingress​

​​ 的部分,我这里部署一下 ​

​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 个没启用,我翻译了一下,结果如下。

使用 jenkins 构建 CI/CD 平台

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

[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      
使用 jenkins 构建 CI/CD 平台

这样就可以了撒,部署完了,所有 ​

​Node​

​​ 节点都有运行一个 ​

​nginx-ingress​

​​ 控制器,除了两个 ​

​master​

​​ 节点,因为我没允许 ​

​master​

​​ 运行 ​

​Pod​

​​,​

​Ingress​

​​ 部署完了,现在可以部署 ​

​jenkins​

​ 了。

部署 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      
使用 jenkins 构建 CI/CD 平台

正常启动了,写个 ​

​hosts​

​ 就访问就可以了撒。

使用 jenkins 构建 CI/CD 平台

不用去看这个文件,​

​pod​

​​ 日志里有输出密码,直接看 ​

​pod​

​ 日志就行了。

[root@master-1 ~]# kubectl logs jenkins-0      
使用 jenkins 构建 CI/CD 平台

之后就需要装插件了,不装任何插件,一会用啥子装啥子,

使用 jenkins 构建 CI/CD 平台

现在不装插件最主要的原因其实就是有些插件装了需要重启 ​

​jenkins​

​​ 才可以,如果现在装完了插件完成后续操作后会回到 ​

​jenkins​

​​ 主页面,但是 ​

​jenkins​

​​ 主页面就是一片空白,需要重启 ​

​jenkins​

​​ 才可以,当然 ​

​pod​

​​ 是没有重启这个概念的,你得想办法把之前的 ​

​pod​

​​ 弄死,​

​k8s​

​ 帮你重新拉起一个之后就正常了,所以先不装插件,正常的话完成后续操作就可以看到主页面了。

使用 jenkins 构建 CI/CD 平台

现在 ​

​jenkins​

​​ 是装完了,下面需要 ​

​jenkins​

​ 和集群融合一下了。

jenkins 在 k8s 动态创建代理

现在需要 ​

​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 构建 CI/CD 平台

装完之后需要配置一下这个插件,使 ​

​jenkins​

​​ 支持 ​

​kubernetes​

​​,点开系统管理→系统设置→拉到最下面,可以看到这个,直接点进去,​

​Kubernetes​

​​ 这里的话配这样就行了,这里写的都是 ​

​dns​

​ 名称。

使用 jenkins 构建 CI/CD 平台

测试连接莫得问题,凭据和 ​

​key​

​​ 都不用到,都已经 ​

​rbac​

​​ 授权了,如果是部署在集群外 ​

​key​

​​ 那里就需要写 ​

​apiserver​

​​ 的 ​

​CA​

​​ 信息了,再然后就是配置 ​

​jenkins​

​ 这里了。

使用 jenkins 构建 CI/CD 平台

这样就可以了,如果你的 ​

​jenkins​

​​ 在集群外 ​

​kubernetes​

​​ 地址就不要写内部 ​

​DNS​

​​ 名称了,​

​key​

​​ 那里需要配置 ​

​apiserver​

​​ 的 ​

​CA​

​​ 证书就可以了,下面还有一个添加 ​

​pod​

​ 模板,也就是这个,

使用 jenkins 构建 CI/CD 平台

这个东西就是定义如何创建 ​

​slave pod​

​​ 的一个模板,你可以理解为这里就是一个 ​

​YAML​

​​ 文件定义了怎么去创建 ​

​slave-pod​

​​,只不过是通过 ​

​UI​

​​ 的形式去配置,这一块也可以通过 ​

​Pipeline​

​​ 脚本去定义,所以不需要在这里配置这个了,如果在这里配置你可能每次添加一个项目都要在这里配置一次,不方便管理,在 ​

​Pipeline​

​​ 里面配置就比较方便了,所以目前只需要配置怎么连接 ​

​kubernetes​

​​ 就可以了,这个差掉,保存退出就可以了,下面开始构建 ​

​slave​

​ 镜像。

构建 jenkins-slave 镜像

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

​Pipeline​

使用 jenkins 构建 CI/CD 平台

这次装的比较多,让他装着吧,装完后自动重启,一会会写一个 ​

​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 .      
使用 jenkins 构建 CI/CD 平台

这样就构建完了,然后把这个镜像推送到镜像仓库,我不用刚刚搭建的 ​

​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​

​​ 来完成,​

​Pipeline​

​​ 是通过特定的语法从简单到复杂的传输管道进行建模,支持两种定义的方式,一种为声明式,遵循 ​

​Grovvy​

​​ 相同语法,使用 ​

​pipeline {}​

​​,还有一种是脚本式,支持 ​

​Grovvy​

​​ 大部分功能,也是表达灵活的工具,​

​node {}​

​,这两种使用哪个都可以,看一下官方的栗子。

示例地址,

声明式

使用 jenkins 构建 CI/CD 平台

脚本式

使用 jenkins 构建 CI/CD 平台

我的头开始大了,后面主要是使用脚本式,这东西的定义就是一个文本文件,也称为 ​

​Jenkinsfile​

​,下面创建一个流水线任务来玩玩。

创建流水线任务

自行创建吧,创建完任务之后拉到最下面,选这个,会自动补全一个例子,改改这个例子。

使用 jenkins 构建 CI/CD 平台

改成这样。

使用 jenkins 构建 CI/CD 平台

这样就可以了,保存,然后构建一下,构建成功后你会看到这个。

使用 jenkins 构建 CI/CD 平台

可以看到刚刚定义的三个步骤以图表的形式展现了出来,这是一个最简单的示例,脚本里面都有多个 ​

​stage​

​​,这个 ​

​stage​

​​ 是脚本最基本的组成部分,它用来告诉 ​

​jenkins​

​​ 要去干什么,之后就需要在这个脚本中实现从代码拉取到部署到 ​

​k8s​

​ 的全部过程,官方的原理图。

使用 jenkins 构建 CI/CD 平台

说白了还是完成了从构建到发布的流程,所以接下来就开始搞在脚本中完成这些操作。

拉取代码

第一步就是拉取代码,到底怎么拉,这些步骤的语法都可以通过 ​

​Pipeline​

​ 脚本语法去帮我们生成,这个是重点,也就是这个位置。

使用 jenkins 构建 CI/CD 平台

新到一个页面,譬如我现在要拉取 ​

​git​

​​ 仓库 ​

​rj-bai.git​

​ 的代码,我就可以这样做了,

使用 jenkins 构建 CI/CD 平台

使用 ​

​master​

​​ 分支,但是提示无法连接到这个仓库,这里也是需要免交互拉取代码的,之前的操作就是将 ​

​master​

​​ 公钥拷贝到了 ​

​git​

​​ 服务器,所以现在要用到私钥了,需要添加一个凭据,将用户名私钥添加进去添加即可,如果你是 ​

​SVN​

​​ 请添加用户名密码,​

​SVN​

​ 怎么拉代码下面会提到,

使用 jenkins 构建 CI/CD 平台

添加后回到主页面之后报错没了,说明可以免交互拉取代码了,点击 ​

​Generate Pipeline Script​

​ 之后就会生拉取代码的脚本了。

使用 jenkins 构建 CI/CD 平台

复制这一串子贴到这个位置就可以拉取代码了,我顺便执行了一条 ​

​shell​

​ 命令

使用 jenkins 构建 CI/CD 平台

保存构建,看了一下 ​

​log​

​​ 输出,之前添加的 ​

​Index.html​

​ 已经拉取下来了,说明拉取代码这块莫得问题。

使用 jenkins 构建 CI/CD 平台

能拉取代码了,下一步就要开始编译了,下面看一下如何编译代码。

编译代码

上面执行的这些任务都是在 ​

​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: '[email protected]:/home/rj-bai/rj-bai.git'
       sh 'ls -l'
   }
   stage('建构') {
   }
   stage('部署') {
   }
}
}      

上面的那一段就是定义 ​

​slave​

​​ 节点的声明,使用 ​

​jnlp​

​​ 协议,镜像我传到了阿里云的仓库,这个是公开的,一会在构建的时候他回去拉这个镜像作为 ​

​slave​

​​ 启动,我直接也把 ​

​docker​

​​ 挂进去了,方便构建推送镜像,这样配置之后构建镜像就不是由 ​

​master​

​​ 来完成的了,而是由动态创建的 ​

​slave​

​​ 来完成的,测试一下,保存,然后构建,动态查看 ​

​pod​

​ 状态。

使用 jenkins 构建 CI/CD 平台

这里构建成功了,看一下 ​

​k8s​

​​ 集群中 ​

​pod​

​ 的变化。

使用 jenkins 构建 CI/CD 平台

这就是动态创建 ​

​slave​

​​ 了,在需要的时候创建一个,​

​slave​

​ 完成任务后就会被销毁,这一块没啥子问题,下面准备一下要编译的源代码。

准备 java 源代码

既然是编译,你就需要有 ​

​java​

​​ 的源码了,建议在这里生成一个,目前只要有简单的 ​

​web​

​​ 访问就够了,所以选这个,编译成功后会有一个 ​

​jar​

​​ 文件,​

​jdk​

​​ 版本 ​

​1.8​

​,这样就可以了。

使用 jenkins 构建 CI/CD 平台

选完点击绿色按钮你会下载一个名为 ​

​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 [email protected]:/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      
使用 jenkins 构建 CI/CD 平台

这样就可以了撒,其实 ​

​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 = "[email protected]:/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​

​,很久之前在别的文章里提过这个,先来解释一下我镜像仓库那里为什么这样写,看一下我镜像仓库完整的地址你就懂了。

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

如果你是自建的镜像仓库可以不加 ​

​namespace​

​​ 的配置,我这里就必须得加了,还有一些奇怪的 ​

​ID​

​,下面分别来解释一下都是用来干嘛的。

​docker_registry_auth​

​​ 是用来拉取推送镜像的凭据,但是那里写的是凭据的 ​

​ID​

​​,这个凭证是 ​

​slave-pod​

​ 所使用的,因为他需要向私有仓库推送镜像,需要登陆后才能去推送镜像,添加方法如下。

主页面→凭据→系统→全局凭据→添加,类型就是用户名密码,然后填进去点 ​

​ok​

​​ 就可以了,​

​ID​

​ 会自动生成一个。

使用 jenkins 构建 CI/CD 平台

添加之后会自动返回,然后点更新的按钮就可以查看到 ​

​ID​

​ 了,要和脚本中的对应上。

使用 jenkins 构建 CI/CD 平台

再下面的那个 ​

​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']])      
使用 jenkins 构建 CI/CD 平台

在看构建镜像那里也有奇怪的一段,也就是这个,

withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {      

这一段是用来保存登陆镜像仓库的用户名和密码,以变量的形式,这样做的主要原因就是不会让用户名密码的明文暴露在 ​

​pipeline​

​ 脚本里,也是动态生成的,如果所示。

使用 jenkins 构建 CI/CD 平台

和我脚本中的一致,只不过我脚本中定义的是 ​

​ID​

​​ 的变量,说白了这样做就是将用户名存到了名为 ​

​username​

​​ 的变量中,密码保存到了 ​

​password​

​​ 变量中,直接引用就完了,不会暴露你用户名密码明文,就是这个原理,再下面写了一个 ​

​Dockerfile​

​​ 和一个启动脚本,现在还有一个变量没有去定义,也就是 ​

​Branch​

​​,用来定义要构建的分支,​

​git​

​​ 的分支会有很多,不止是一个 ​

​master​

​,所以说不是固定的,所以现在定义一下这个变量。

编辑这个 ​

​job​

​​,找到参数化构建过程,配置成这样,使用 ​

​SVN​

​ 的不用改这里撒,

使用 jenkins 构建 CI/CD 平台

这就是一个变量,默认值是 ​

​master​

​​,保存回到主界面,点 ​

​Build with Parameters​

​,会看到这种效果。

使用 jenkins 构建 CI/CD 平台

中途失败了好多次,各种调试,之前也成功过,但是第 25 次是才是真正意义上的成功,

使用 jenkins 构建 CI/CD 平台

现在镜像已经推送到我阿里云的仓库了,直接在服务器上拉一下吧,试着运行一下。

[root@kubeadm-node ~]# docker run -d -p 666:8080 registry.cn-beijing.aliyuncs.com/rj-bai/webstarter:25      
使用 jenkins 构建 CI/CD 平台

莫得问题,可以正常访问,说明之前的步骤都没问题了,现在通过 ​

​pipeline​

​​ 完成了 ​

​CI​

​​ 阶段,拉取代码编译构建镜像推送镜像,感觉是比之前的方便很多了,下一步就是需要将刚刚的东西部署到 ​

​K8s​

​​ 中了,也就是 ​

​CD​

​ 阶段。

jenkins 在 k8s 中持续部署

上面镜像已经准备好了,现在该实现自动部署到 ​

​k8s​

​​ 中了,要想实现这个还需要一个插件,名为 ​

​kubernetes continuous deploy​

​​,用于将资源部署到 ​

​k8s​

​​ 中,他支持绝大部分的资源类型,像是什么 ​

​deployment&service​

​,自行安装吧,简单看一下这个插件的介绍。

官方地址,主要看一下这一段,这是配置在 ​

​pipeline​

​ 中使用的写法

使用 jenkins 构建 CI/CD 平台

​kubeconfigId​

​​ 这里是需要指定一个 ​

​kubeconfig​

​​ 的 ​

​ID​

​​,这个东西就是用于连接 ​

​k8s​

​​ 的一个配置文件,所以我们要把这个文件内容保存到 ​

​jenkins​

​​ 中作为凭据,然后去引用凭据的 ​

​ID​

​。

​config​

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

​YAML​

​ 文件。

secretNamespace: '<secret-namespace>',
                 secretName: '<secret-name>',      

这块是用来指定 ​

​secret​

​​ 的,有两个是必须的,一个是 ​

​kubeconfig​

​​ 文件,再就是资源文件,这个插件的 ​

​pipeline​

​ 写法也是可以生成的,所以还是用之前的工具来生成一下。

使用 jenkins 构建 CI/CD 平台

现在开始让你添加 ​

​kubeconfig​

​ 的文件了,添加吧,选择这个,

使用 jenkins 构建 CI/CD 平台

这个文件具体要怎么获取,如果你的集群是 ​

​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 = "[email protected]:/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​

​ 的状态,先看页面,显示成功。

使用 jenkins 构建 CI/CD 平台

再看 ​

​pod​

​ 的状态,都已经正常启动了,

使用 jenkins 构建 CI/CD 平台

然后手写 ​

​hosts​

​ 访问一下,

使用 jenkins 构建 CI/CD 平台

莫得问题,就是这种效果,现在也完成了持续部署,生成的 ​

​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​

​ 的方式一致

[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 = "[email protected]:/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​

​,配置如下,

使用 jenkins 构建 CI/CD 平台

​Branches to build​

​​ 里指定的是要在哪个分支去拉取 ​

​Jenkinsfile​

​​,我们这里指定 ​

​master​

​​ 就可以了,这样配置后脚本就不用写在 ​

​jenkins​

​ 里了,保存开始构建吧,这次是一次就成功了,

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

写 ​

​hosts​

​ 访问一下,

使用 jenkins 构建 CI/CD 平台

啊,就是这样,能访问到,说明没啥子问题,说真的这个博客做的还是蛮不错的,而且看上去开源的原因也是作者被逼无奈,哈哈

说实话部署这个博客的流程和我部署我们公司项目的流程是一样的,先部署项目需要的基础服务,基础服务部署完成后就可以部署项目了,部署完项目之后将项目发布出来,当然之前是手写 ​

​nginx​

​​ 配置文件,现在用的是 ​

​nginx-ingress​

​​,这个东西真的好方便,自动关联 ​

​service​

​​ 后端 ​

​pod​

​,缺点之前也提过,只能针对域名,不能针对端口。

刚刚扯到了基础服务,目前公司部署项目依赖的基础服务可不止 ​

​mysql&redis​

​​,之前写 ​

​swarm​

​​ 实战的时候提到过我们这里会用到的基础服务,之后如果没什么意外的话就开始做如何让这些基础服务跑在 ​

​k8s​

​ 上了。

其实还有一个最蛋疼的问题没有解决,就是每添加一个项目你就要创建一个任务、写一个资源文件、写一个 ​

​Jenkinsfile​

​,像我之前用的不是流水线,用的这个,

使用 jenkins 构建 CI/CD 平台

每次加一个项目我这里就要创建一个任务,然后写两个 ​

​playbook​

​​,一个更新的一个回滚的,看一哈我之前写的,有 ​

​29​

​​ 个项目写了 ​

​29​

​​ 个发布的 ​

​playbook​

使用 jenkins 构建 CI/CD 平台

再之后 ​

​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​

​ 了,看到这个错就是内存占用超了我的资源限制,所以就被杀了,所以在做资源限制的时候也要慎重,但这东西还不能不做,不做的话可能会给你的宿主机带来麻烦,现在已经更新完了。

使用 jenkins 构建 CI/CD 平台

先看一下 ​

​Deployment​

​ 的详情,会有事件显示,主要就是这里,

使用 jenkins 构建 CI/CD 平台

可以看到在触发滚动更新的时候新创建了一个名为 ​

​web-5c466f6896​

​​ 的 ​

​replicasets​

​​,设置它的副本数为 ​

​1​

​​,这就是用来跑新版本的,这个正常启动之后下一步操作是把名为 ​

​web-c98f55d49​

​​ 的 ​

​replicasets​

​​ 副本调整成了 ​

​2​

​​,这是旧的 ​

​replicasets​

​​,所以这时候就是有两个旧版本一个新版本在跑,注意这不是同时进行的,是新的启动成功之后才会去停旧的,如果新的启动失败了,滚动更新就会暂停,不会影响旧的 ​

​replicasets​

​​,最终结果就是新的 ​

​web-5c466f6896​

​​ 副本数设置为 ​

​3​

​​,旧的 ​

​web-c98f55d49​

​​ 设置成 ​

​0​

​​,下面看一下 ​

​ReplicaSet​

​ 就明白了。

使用 jenkins 构建 CI/CD 平台

可以看到旧的 ​

​replicasets​

​​ 副本数已经被设置成 ​

​0​

​​ 了,创建时间是 ​

​26h​

​​ 以前,新的创建于 ​

​12m​

​​ 之前,副本数被设置成 ​

​3​

​​,这样就理解了撒,过程就是这样,回滚的话就是反向操作,先设置上一个版本的 ​

​replicasets​

​​ 的副本数为 ​

​1​

​​,一个旧的正常启动后缩减一个当前的 ​

​replicasets​

​,我回滚了一下,看图吧。

[root@master-1 ~]# kubectl rollout undo deployment web
deployment.extensions/web rolled back      
使用 jenkins 构建 CI/CD 平台

更新回滚的原理就是这样的,其实你可以利用他这个原理去实现灰度发布,灰度发布是啥子呢?说白了就是先升级部分的服务,譬如你有 ​

​10​

​​ 个 ​

​pod​

​​,我先升级两个,这时候就集群中就又有 ​

​2​

​​ 个新的 ​

​8​

​​ 个旧的,会有一小部分用户去访问,如果新版本用户没反馈就开始扩大范围,我再升级 ​

​3​

​​ 个,现在就一半一半了,如果用户还没啥子反馈就全部升级到新版本,这种灰度发布当前 ​

​K8s​

​ 是不支持的,变通一下就可以实现,思路如下。

需要使用两套 ​

​deployment​

​​,上面提到了,​

​deployment​

​​ 会通过 ​

​replicasets​

​​ 实现新旧版本的更新,灰度发布实现的原理和他是样的,譬如现在我要灰度发布,现在已经有一个 ​

​deployment​

​​ 了,我再创建一个部署新版应用的 ​

​deployment​

​​,当然这个 ​

​deployment​

​​ 的名字不要和之前一样撒,相同的地方是这两个的 ​

​deployment​

​​ 所关联的 ​

​service​

​​ 是同一个,这样当有新的 ​

​pod​

​​ 启动后就会和旧的 ​

​pod​

​​ 并行提供服务,这个是重点,结合上面提到的东西,我先把新的 ​

​deployment​

​​ 所关联的 ​

​replicasets​

​​ 进行扩展,譬如目前老版本有 ​

​10​

​​ 个 ​

​pod​

​​,我先启动两个新的,这两个新的启动完成之后我再去缩容旧的 ​

​pod​

​​ 的数量,譬如我缩容到八个,这时候就有两个新的八个旧的并行提供服务了,反馈没问题之后再进行同样的操作,直到将新的扩容到 ​

​10​

​​,旧的缩容成 ​

​0​

​,懂我啥意思了吧,这样就实现了灰度发布。

这样做的好处是影响范围可控,灰度发布是目前比较主流的方案,想要实现这个方案有一个很大的难点,就是去控制每一波升级缩容的数量,要扩容多少新的,缩减多少旧的,就白了就是你要有效控制新的扩容旧的缩容,最简单的办法就是手动去 ​

​scale​

​,哈哈,最好的实现方法还是在程序层面去控制,思路就是这样,下面来看看滚动更新的策略。

滚动更新策略

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

继续阅读