From e5b433885030cf22504dc2cae7997b476d28c119 Mon Sep 17 00:00:00 2001 From: yangchuanhu Date: Wed, 24 Apr 2019 15:31:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E5=BE=AE=E5=8D=9A=E5=9B=BE?= =?UTF-8?q?=E5=BA=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 1 + ...r \347\233\221\346\216\247\351\241\271.md" | 20 +- ...30\347\272\247\351\205\215\347\275\256.md" | 4 +- ...45\345\277\227\347\263\273\347\273\237.md" | 12 +- docs/63.Harbor.md | 12 +- docs/64.Gitlab.md | 10 +- docs/65.Gitlab CI.md | 20 +- docs/66.devops.md | 603 ++++++++++++++++++ gitlab-ci-k8s-demo | 1 + 9 files changed, 644 insertions(+), 39 deletions(-) create mode 100644 docs/66.devops.md create mode 160000 gitlab-ci-k8s-demo diff --git a/SUMMARY.md b/SUMMARY.md index 4786a8a..52aa78d 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -99,4 +99,5 @@ * [Harbor](docs/63.Harbor.md) * [Gitlab](docs/64.Gitlab.md) * [Gitlab CI](docs/65.Gitlab CI.md) +* [Devops](docs/66/devops.md) diff --git "a/docs/59.\350\207\252\345\256\232\344\271\211Prometheus Operator \347\233\221\346\216\247\351\241\271.md" "b/docs/59.\350\207\252\345\256\232\344\271\211Prometheus Operator \347\233\221\346\216\247\351\241\271.md" index e1948a0..ac1fa54 100644 --- "a/docs/59.\350\207\252\345\256\232\344\271\211Prometheus Operator \347\233\221\346\216\247\351\241\271.md" +++ "b/docs/59.\350\207\252\345\256\232\344\271\211Prometheus Operator \347\233\221\346\216\247\351\241\271.md" @@ -140,7 +140,7 @@ spec: 上面我们在 monitoring 命名空间下面创建了名为 etcd-k8s 的 ServiceMonitor 对象,基本属性和前面章节中的一致,匹配 kube-system 这个命名空间下面的具有 k8s-app=etcd 这个 label 标签的 Service,jobLabel 表示用于检索 job 任务名称的标签,和前面不太一样的地方是 endpoints 属性的写法,配置上访问 etcd 的相关证书,endpoints 属性下面可以配置很多抓取的参数,比如 relabel、proxyUrl,tlsConfig 表示用于配置抓取监控数据端点的 tls 认证,由于证书 serverName 和 etcd 中签发的可能不匹配,所以加上了 insecureSkipVerify=true -![tlsConfig](https://ws4.sinaimg.cn/large/006tNbRwgy1fy9s4embvlj313a0fmdik.jpg) +![tlsConfig](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/mI32WB.jpg) > 关于 ServiceMonitor 属性的更多用法可以查看文档:[https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md) 了解更多 @@ -195,7 +195,7 @@ $ kubectl create -f prometheus-etcdService.yaml 创建完成后,隔一会儿去 Prometheus 的 Dashboard 中查看 targets,便会有 etcd 的监控项了: -![prometheus etcd](https://ws4.sinaimg.cn/large/006tNbRwgy1fy9smdbdgwj31y80autbj.jpg) +![prometheus etcd](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/5BQRte.jpg) 可以看到还是有一个明显的错误,和我们上节课监控 kube-scheduler 的错误比较类似于,因为我们这里的 etcd 的是监听在 127.0.0.1 这个 IP 上面的,所以访问会拒绝: ```shell @@ -209,17 +209,17 @@ $ kubectl create -f prometheus-etcdService.yaml 重启 etcd,生效后,查看 etcd 这个监控任务就正常了: -![prometheus etcd](https://ws4.sinaimg.cn/large/006tNbRwgy1fy9st81y3tj31yc0a0gnq.jpg) +![prometheus etcd](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/EmEn6b.jpg) 数据采集到后,可以在 grafana 中导入编号为`3070`的 dashboard,获取到 etcd 的监控图表。 -![grafana etcd dashboard](https://ws3.sinaimg.cn/large/006tNbRwgy1fy9t1ewpc5j31uf0u00wj.jpg) +![grafana etcd dashboard](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/yQgrwt.jpg) ### 配置 PrometheusRule 现在我们知道怎么自定义一个 ServiceMonitor 对象了,但是如果需要自定义一个报警规则的话呢?比如现在我们去查看 Prometheus Dashboard 的 Alert 页面下面就已经有一些报警规则了,还有一些是已经触发规则的了: -![alerts](https://ws3.sinaimg.cn/large/006tNbRwgy1fyc4hf239zj313i0p6juv.jpg) +![alerts](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/DADO6K.jpg) 但是这些报警信息是哪里来的呢?他们应该用怎样的方式通知我们呢?我们知道之前我们使用自定义的方式可以在 Prometheus 的配置文件之中指定 AlertManager 实例和 报警的 rules 文件,现在我们通过 Operator 部署的呢?我们可以在 Prometheus Dashboard 的 Config 页面下面查看关于 AlertManager 的配置: ```yaml @@ -355,7 +355,7 @@ monitoring-etcd-rules.yaml monitoring-prometheus-k8s-rules.yaml 可以看到我们创建的 rule 文件已经被注入到了对应的 rulefiles 文件夹下面了,证明我们上面的设想是正确的。然后再去 Prometheus Dashboard 的 Alert 页面下面就可以查看到上面我们新建的报警规则了: -![etcd cluster](https://ws2.sinaimg.cn/large/006tNbRwgy1fyc62d4gkzj31540doac1.jpg) +![etcd cluster](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/n68RSK.jpg) ### 配置报警 @@ -363,7 +363,7 @@ monitoring-etcd-rules.yaml monitoring-prometheus-k8s-rules.yaml 首先我们将 alertmanager-main 这个 Service 改为 NodePort 类型的 Service,修改完成后我们可以在页面上的 status 路径下面查看 AlertManager 的配置信息: -![alertmanager config](https://ws4.sinaimg.cn/large/006tNbRwgy1fyc6dz3t5ij31aa0u0te0.jpg) +![alertmanager config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/Ty1Gxu.jpg) 这些配置信息实际上是来自于我们之前在`prometheus-operator/contrib/kube-prometheus/manifests`目录下面创建的 alertmanager-secret.yaml 文件: @@ -440,15 +440,15 @@ secret "alertmanager-main" created 我们添加了两个接收器,默认的通过邮箱进行发送,对于 CoreDNSDown 这个报警我们通过 webhook 来进行发送,这个 webhook 就是我们前面课程中定义的一个钉钉接收的 Server,上面的步骤创建完成后,很快我们就会收到一条钉钉消息: -![钉钉](https://ws3.sinaimg.cn/large/006tNbRwgy1fyc7drk445j311c0mm7bo.jpg) +![钉钉](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/Of4GIB.jpg) 同样邮箱中也会收到报警信息: -![邮箱](https://ws1.sinaimg.cn/large/006tNbRwgy1fyc7j3k20vj30u00u4gpx.jpg) +![邮箱](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/NjnV2X.jpg) 我们再次查看 AlertManager 页面的 status 页面的配置信息可以看到已经变成上面我们的配置信息了: -![alertmanager config](https://ws1.sinaimg.cn/large/006tNbRwgy1fyc7kvs27vj30vp0u017c.jpg) +![alertmanager config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/gKhiPI.jpg) AlertManager 配置也可以使用模板(.tmpl文件),这些模板可以与 alertmanager.yaml 配置文件一起添加到 Secret 对象中,比如: diff --git "a/docs/60.Prometheus Operator\351\253\230\347\272\247\351\205\215\347\275\256.md" "b/docs/60.Prometheus Operator\351\253\230\347\272\247\351\205\215\347\275\256.md" index 90ecbf4..0c105cf 100644 --- "a/docs/60.Prometheus Operator\351\253\230\347\272\247\351\205\215\347\275\256.md" +++ "b/docs/60.Prometheus Operator\351\253\230\347\272\247\351\205\215\347\275\256.md" @@ -108,7 +108,7 @@ prometheus.monitoring.coreos.com "k8s" configured 隔一小会儿,可以前往 Prometheus 的 Dashboard 中查看配置是否生效: -![config](https://ws1.sinaimg.cn/large/006tNbRwgy1fyd8c25rf2j31530u0dl5.jpg) +![config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/jmiqaD.jpg) 在 Prometheus Dashboard 的配置页面下面我们可以看到已经有了对应的的配置信息了,但是我们切换到 targets 页面下面却并没有发现对应的监控任务,查看 Prometheus 的 Pod 日志: ```shell @@ -172,7 +172,7 @@ rules: 更新上面的 ClusterRole 这个资源对象,然后重建下 Prometheus 的所有 Pod,正常就可以看到 targets 页面下面有 kubernetes-service-endpoints 这个监控任务了: -![endpoints](https://ws2.sinaimg.cn/large/006tNbRwgy1fyd9q7pq78j31rg0ewtca.jpg) +![endpoints](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/R38S3q.jpg) 我们这里自动监控了两个 Service,第一个就是我们之前创建的 Redis 的服务,我们在 Redis Service 中有两个特殊的 annotations: ```yaml diff --git "a/docs/62.\346\220\255\345\273\272 EFK \346\227\245\345\277\227\347\263\273\347\273\237.md" "b/docs/62.\346\220\255\345\273\272 EFK \346\227\245\345\277\227\347\263\273\347\273\237.md" index 33b8c67..9f963a5 100644 --- "a/docs/62.\346\220\255\345\273\272 EFK \346\227\245\345\277\227\347\263\273\347\273\237.md" +++ "b/docs/62.\346\220\255\345\273\272 EFK \346\227\245\345\277\227\347\263\273\347\273\237.md" @@ -439,7 +439,7 @@ kibana NodePort 10.105.208.253 5601:31816/TCP 2 如果 Pod 已经是 Running 状态了,证明应用已经部署成功了,然后可以通过 NodePort 来访问 Kibana 这个服务,在浏览器中打开`http://<任意节点IP>:31816`即可,如果看到如下欢迎界面证明 Kibana 已经成功部署到了 Kubernetes集群之中。 -![kibana welcome](https://ws3.sinaimg.cn/large/006tNc79gy1fz9h04vmnnj316o0tktdh.jpg) +![kibana welcome](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/FXJOqE.jpg) ## 部署 Fluentd @@ -452,7 +452,7 @@ Fluentd 通过一组给定的数据源抓取日志数据,处理后(转换成 * 结构化并且标记这些数据 * 然后根据匹配的标签将数据发送到多个目标服务去 -![fluentd 架构](https://ws4.sinaimg.cn/large/006tNc79gy1fz9hctkjysj30xc0ge0tk.jpg) +![fluentd 架构](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/7moPNc.jpg) ### 配置 @@ -826,15 +826,15 @@ kibana-7558d4dc4d-5mqdz 1/1 Running 0 1d Fluentd 启动成功后,我们可以前往 Kibana 的 Dashboard 页面中,点击左侧的`Discover`,可以看到如下配置页面: -![create index](https://ws3.sinaimg.cn/large/006tNc79gy1fz9nkcfrrrj31cf0u00y2.jpg) +![create index](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/gSH5TE.jpg) 在这里可以配置我们需要的 Elasticsearch 索引,前面 Fluentd 配置文件中我们采集的日志使用的是 logstash 格式,这里只需要在文本框中输入`logstash-*`即可匹配到 Elasticsearch 集群中的所有日志数据,然后点击下一步,进入以下页面: -![index config](https://ws2.sinaimg.cn/large/006tNc79gy1fz9noes54aj31di0u043y.jpg) +![index config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/rLJ1wS.jpg) 在该页面中配置使用哪个字段按时间过滤日志数据,在下拉列表中,选择`@timestamp`字段,然后点击`Create index pattern`,创建完成后,点击左侧导航菜单中的`Discover`,然后就可以看到一些直方图和最近采集到的日志数据了: -![log data](https://ws4.sinaimg.cn/large/006tNc79gy1fz9ntqiplvj31df0u04bf.jpg) +![log data](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/U5d7oL.jpg) ### 测试 @@ -861,7 +861,7 @@ $ kubectl create -f counter.yaml Pod 创建并运行后,回到 Kibana Dashboard 页面,在上面的`Discover`页面搜索栏中输入`kubernetes.pod_name:counter`,就可以过滤 Pod 名为 counter 的日志数据: -![counter log data](https://ws4.sinaimg.cn/large/006tNc79gy1fz9o3c5ds8j31df0u0qg5.jpg) +![counter log data](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/Dd5VCx.jpg) 我们也可以通过其他元数据来过滤日志数据,比如 您可以单击任何日志条目以查看其他元数据,如容器名称,Kubernetes 节点,命名空间等。 diff --git a/docs/63.Harbor.md b/docs/63.Harbor.md index 165496e..5188e95 100644 --- a/docs/63.Harbor.md +++ b/docs/63.Harbor.md @@ -1,5 +1,5 @@ # 63. Harbor -[![harbor](https://ws4.sinaimg.cn/large/006tKfTcgy1g0cz18sku5j321r0kz0ux.jpg)](/post/harbor-code-analysis/) +[![harbor](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/5xvK5f.jpg)](/post/harbor-code-analysis/) [Harbor](https://github.com/goharbor/harbor) 是一个`CNCF`基金会托管的开源的可信的云原生`docker registry`项目,可以用于存储、签名、扫描镜像内容,Harbor 通过添加一些常用的功能如安全性、身份权限管理等来扩展 docker registry 项目,此外还支持在 registry 之间复制镜像,还提供更加高级的安全功能,如用户管理、访问控制和活动审计等,在新版本中还添加了`Helm`仓库托管的支持。 @@ -19,7 +19,7 @@ 至此,整个登录过程完成,整个过程可以用下面的流程图来说明: -![docker login](https://ws4.sinaimg.cn/large/006tKfTcgy1g0cwrqk1mqj310q0iuwgt.jpg) +![docker login](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/1WZx0K.jpg) 要完成上面的登录认证过程有两个关键点需要注意:怎样让 registry 服务知道服务认证地址?我们自己提供的认证服务生成的 token 为什么 registry 就能够识别? @@ -616,16 +616,16 @@ harbor-harbor-ingress registry.qikqiak.com,notary.qikqiak.com 80, ### Harbor Portal 添加完成后,在浏览器中输入`registry.qikqiak.com`就可以打开熟悉的 Harbor 的 Portal 界面了,当然我们配置的 Ingress 中会强制跳转到 https,所以如果你的浏览器有什么安全限制的话,需要信任我们这里 Ingress 对应的证书,证书文件可以通过查看 Secret 资源对象获取: -![Harbor Portal](https://ws2.sinaimg.cn/large/006tKfTcgy1g0f8ojkpikj31dy0u0wi4.jpg) +![Harbor Portal](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/xg1GWO.jpg) 然后输入用户名:admin,密码:Harbor12345(当然我们也可以通过 Helm 安装的时候自己覆盖 harborAdminPassword)即可登录进入 Portal 首页: -![Harbor Portal Home](https://ws2.sinaimg.cn/large/006tKfTcgy1g0f8qo55p0j31d80u0q7t.jpg) +![Harbor Portal Home](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/MA7tef.jpg) 我们可以看到有很多功能,默认情况下会有一个名叫`library`的项目,改项目默认是公开访问权限的,进入项目可以看到里面还有 Helm Chart 包的管理,可以手动在这里上传,也可以对改项目里面的镜像进行一些配置,比如是否开启自动扫描镜像功能: -![Harbor project settings](https://ws1.sinaimg.cn/large/006tKfTcgy1g0f98e41i1j31230u0777.jpg) +![Harbor project settings](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/XieDpz.jpg) ### docker cli @@ -690,7 +690,7 @@ latest: digest: sha256:4415a904b1aca178c2450fd54928ab362825e863c0ad5452fd020e92f 推送完成后,我们同样可以在 Portal 页面上看到这个镜像的信息: -![Harbor image info](https://ws1.sinaimg.cn/large/006tKfTcgy1g0f9woj4otj318q0u0n1o.jpg) +![Harbor image info](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/rGY1hl.jpg) 镜像 push 成功,同样可以测试下 pull: diff --git a/docs/64.Gitlab.md b/docs/64.Gitlab.md index 7f79f65..6cfe60e 100644 --- a/docs/64.Gitlab.md +++ b/docs/64.Gitlab.md @@ -287,17 +287,17 @@ redis-8446f57bdf-4v62p 1/1 Running 0 17 可以看到都已经部署成功了,然后我们可以通过 Ingress 中定义的域名`git.qikqiak.com`(需要做 DNS 解析或者在本地 /etc/hosts 中添加映射)来访问 Portal: -![gitlab portal](https://ws1.sinaimg.cn/large/006tKfTcgy1g0u81axrwlj318y0u0wj0.jpg) +![gitlab portal](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/Fxx65D.jpg) 使用用户名 root,和部署的时候指定的超级用户密码`GITLAB_ROOT_PASSWORD=admin321`即可登录进入到首页: -![gitlab homepage](https://ws3.sinaimg.cn/large/006tKfTcgy1g0u85rzqbfj311g0u0jx1.jpg) +![gitlab homepage](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/WhSLdg.jpg) Gitlab 运行后,我们可以注册为新用户并创建一个项目,还可以做很多的其他系统设置,比如设置语言、设置应用风格样式等等。 点击`Create a project`创建一个新的项目,和之前 Github 使用上没有多大的差别: -![create gitlab project](https://ws1.sinaimg.cn/large/006tKfTcgy1g0u8gpdwqoj31h90u0483.jpg) +![create gitlab project](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/VfnfrO.jpg) 创建完成后,我们可以添加本地用户的一个`SSH-KEY`,这样我们就可以通过 SSH 来拉取或者推送代码了。SSH 公钥通常包含在`~/.ssh/id_rsa.pub` 文件中,并以`ssh-rsa`开头。如果没有的话可以使用`ssh-keygen`命令来生成,`id_rsa.pub`里面的内容就是我们需要的 SSH 公钥,然后添加到 Gitlab 中。 @@ -326,7 +326,7 @@ spec: 注意上面 ssh 对应的 nodePort 端口设置为 30022,这样就不会随机生成了,重新更新下 Deployment 和 Service,更新完成后,现在我们在项目上面 Clone 的时候使用 ssh 就会带上端口号了: -![gitlab ssh](https://ws4.sinaimg.cn/large/006tKfTcgy1g0uecaybqfj31kg0kin17.jpg) +![gitlab ssh](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/3978ZL.jpg) 现在就可以使用`Clone with SSH`的地址了,由于上面我们配置了 SSH 公钥,所以就可以直接访问上面的仓库了: ```shell @@ -354,7 +354,7 @@ To ssh://git@git.qikqiak.com:30022/root/gitlab-demo.git 然后刷新浏览器,就可以看到刚刚创建的 Git 仓库中多了一个 README.md 的文件: -![git commit](https://ws4.sinaimg.cn/large/006tKfTcgy1g0uekpjdcfj31b10u0af1.jpg) +![git commit](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/7AiUUZ.jpg) 到这里就表明我们的 Gitlab 就成功部署到了 Kubernetes 集群当中了。 diff --git a/docs/65.Gitlab CI.md b/docs/65.Gitlab CI.md index 4a3fe31..595fcc7 100644 --- a/docs/65.Gitlab CI.md +++ b/docs/65.Gitlab CI.md @@ -81,7 +81,7 @@ To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 前面的章节中我们已经成功安装了 Gitlab,在浏览器中打开`git.qikqiak.com`页面,然后登录后进入到管理页面`http://git.qikqiak.com/admin`,然后点击导航栏中的`Runner`,可以看到该页面中有两个总要的参数,一个是 URL,另外一个就是 Register Token,下面的步骤中需要用到这两个参数值。 -![gitlab runner](https://ws3.sinaimg.cn/large/006tKfTcgy1g188bykw2ij31lu0he0xf.jpg) +![gitlab runner](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/zQbFp1.jpg) > 注意:不要随便泄露 Token @@ -311,7 +311,7 @@ gitlab-ci-runner-1 1/1 Running 0 3m 可以看到已经成功运行了两个(具体取决于`StatefulSet`清单中的副本数) Runner 实例,然后切换到 Gitlab Admin 页面下面的 Runner 页面: -![gitlab runner list](https://ws2.sinaimg.cn/large/006tKfTcgy1g189zhnqzbj31lc0u07bd.jpg) +![gitlab runner list](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/QgxSq3.jpg) 当然我们也可以根据需要更改 Runner 的一些配置,比如添加 tag 标签等。 @@ -331,7 +331,7 @@ $ git push -u origin master ``` 当我们把仓库推送到 Gitlab 以后,应该可以看到 Gitlab CI 开始执行构建任务了: -![gitlab ci](https://ws2.sinaimg.cn/large/006tKfTcgy1g1929udcvuj31mg0owq5b.jpg) +![gitlab ci](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/iN2HFV.jpg) 此时 Runner Pod 所在的 namespace 下面也会出现两个新的 Pod: ```shell @@ -347,7 +347,7 @@ runner-9rixsyft-project-2-concurrent-1t74t9 0/2 ContainerCreating 0 这两个新的 Pod 就是用来执行具体的 Job 任务的,这里同时出现两个证明第一步是并行执行的两个任务,从上面的 Pipeline 中也可以看到是 test 和 test2 这两个 Job。我们可以看到在执行 image_build 任务的时候出现了错误: -![pipeline](https://ws2.sinaimg.cn/large/006tKfTcgy1g19369pzotj31m20emwg4.jpg) +![pipeline](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/iveZ6o.jpg) 我们可以点击查看这个 Job 失败详细信息: ```shell @@ -361,7 +361,7 @@ ERROR: Job failed: command terminated with exit code 1 定位到项目 -> 设置 -> CI/CD,展开`Environment variables`栏目,配置镜像仓库相关的参数值: -![gitlab ci env](https://ws1.sinaimg.cn/large/006tKfTcgy1g19duem1bdj31jc0m6jvl.jpg) +![gitlab ci env](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/ErhBLn.jpg) 配置上后,我们在上面失败的 Job 任务上点击“重试”,在重试过后依然可以看到会出现下面的错误信息: @@ -473,7 +473,7 @@ xxxxxxtoken值xxxx 填写上面对应的值添加集群: -![add k8s cluster](https://ws1.sinaimg.cn/large/006tKfTcgy1g1a6lrep5yj31160u0af9.jpg) +![add k8s cluster](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/wwr2FR.jpg) @@ -779,21 +779,21 @@ $ git push origin master 现在回到 Gitlab 中可以看到我们的项目触发了一个新的 Pipeline 的构建: -![gitlab pipeline](https://ws4.sinaimg.cn/large/006tKfTcgy1g1ag8co9r3j31m80rewhh.jpg) +![gitlab pipeline](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/D54xTd.jpg) 可以查看最后一个阶段(stage)是否正确,如果通过了,证明我们已经成功将应用程序部署到 Kubernetes 集群中了,一个成功的`review`阶段如下所示: -![review success](https://ws3.sinaimg.cn/large/006tKfTcgy1g1ahimbwmwj315z0u0n6w.jpg) +![review success](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/kXE6e7.jpg) 整个 Pipeline 构建成功后,我们可以在项目的环境菜单下面看到多了一个环境: -![env](https://ws1.sinaimg.cn/large/006tKfTcgy1g1ahkx8gwuj31vm0fatap.jpg) +![env](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/pArnyz.jpg) 如果我们点击`终止`,就会调用`.gitlab-ci.yml`中定义的钩子`on_stop: stop_review`,点击`View deployment`就可以看到这次我们的部署结果(前提是DNS解析已经完成): -![view deployment](https://ws3.sinaimg.cn/large/006tKfTcgy1g1ahq5v4qfj313i0hgtbl.jpg) +![view deployment](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/Q2yPG9.jpg) 这就是关于 Gitlab CI 结合 Kubernetes 进行 CI/CD 的过程,具体详细的构建任务还需要结合我们自己的应用实际情况而定。下节课给大家介绍使用 Jenkins + Gitlab + Harbor + Helm + Kubernetes 来实现一个完整的 CI/CD 流水线作业。 diff --git a/docs/66.devops.md b/docs/66.devops.md new file mode 100644 index 0000000..6316c6e --- /dev/null +++ b/docs/66.devops.md @@ -0,0 +1,603 @@ +# 66. Devops +上节课和大家介绍了`Gitlab CI`结合`Kubernetes`进行 CI/CD 的完整过程](https://www.qikqiak.com/post/gitlab-ci-k8s-cluster-feature/)。这节课结合前面所学的知识点给大家介绍一个完整的示例:使用 Jenkins + Gitlab + Harbor + Helm + Kubernetes 来实现一个完整的 CI/CD 流水线作业。 + +其实前面的课程中我们就[已经学习了 Jenkins Pipeline 与 Kubernetes 的完美结合](https://www.qikqiak.com/post/kubernetes-jenkins1/),我们利用 Kubernetes 来动态运行 Jenkins 的 Slave 节点,可以和好的来解决传统的 Jenkins Slave 浪费大量资源的缺点。之前的示例中我们是将项目放置在 Github 仓库上的,将 Docker 镜像推送到了 Docker Hub,这节课我们来结合我们前面学习的知识点来综合运用下,使用 Jenkins、Gitlab、Harbor、Helm、Kubernetes 来实现一个完整的持续集成和持续部署的流水线作业。 + +## 流程 +下图是我们当前示例的流程图 + +![ci/cd demo](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/2D3KxY.jpg) + +* 1. 开发人员提交代码到 Gitlab 代码仓库 +* 2. 通过 Gitlab 配置的 Jenkins Webhook 触发 Pipeline 自动构建 +* 3. Jenkins 触发构建构建任务,根据 Pipeline 脚本定义分步骤构建 +* 4. 先进行代码静态分析,单元测试 +* 5. 然后进行 Maven 构建(Java 项目) +* 6. 根据构建结果构建 Docker 镜像 +* 7. 推送 Docker 镜像到 Harbor 仓库 +* 8. 触发更新服务阶段,使用 Helm 安装/更新 Release +* 9. 查看服务是否更新成功。 + +## 项目 +本次示例项目是一个完整的基于 Spring Boot、Spring Security、JWT、React 和 Ant Design 构建的一个开源的投票应用,项目地址:[https://github.com/callicoder/spring-security-react-ant-design-polls-app](https://github.com/callicoder/spring-security-react-ant-design-polls-app)。 + +![polling app1](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/NTe3id.jpg) + +我们将会在该项目的基础上添加部分代码,并实践 CI/CD 流程。 + +### 服务端 +首先需要更改的是服务端配置,我们需要将数据库链接的配置更改成环境变量的形式,写死了的话就没办法进行定制了,修改服务端文件`src/main/resources/application.properties`,将下面的数据库配置部分修改成如下形式: + +``` +spring.datasource.url= jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:polling_app}?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false +spring.datasource.username= ${DB_USER:root} +spring.datasource.password= ${DB_PASSWORD:root} +``` + +当环境变量中有上面的数据配置的时候,就会优先使用环境变量中的值,没有的时候就会用默认的值进行数据库配置。 + +由于我们要将项目部署到 Kubernetes 集群中去,所以我们需要将服务端进行容器化,所以我们在项目根目录下面添加一个`Dockerfile`文件进行镜像构建: + +```docker +FROM openjdk:8-jdk-alpine + +MAINTAINER cnych + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ENV TZ=Asia/Shanghai + +RUN mkdir /app + +WORKDIR /app + +COPY target/polls-0.0.1-SNAPSHOT.jar /app/polls.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar","/app/polls.jar"] +``` + +由于服务端代码是基于`Spring Boot`构建的,所以我们这里使用一个`openjdk`的基础镜像,将打包过后的`jar`包放入镜像之中,然后用过`java -jar`命令直接启动即可,这里就会存在一个问题了,我们是在 Jenkins 的 Pipeline 中去进行镜像构建的,这个时候项目中并没有打包好的`jar`包文件,那么我们应该如何获取打包好的`jar`包文件呢?这里我们可以使用两种方法: + +第一种就是如果你用于镜像打包的 Docker 版本大于`17.06`版本的话,那么我墙裂推荐你使用 Docker 的多阶段构建功能来完成镜像的打包过程,我们只需要将上面的`Dockerfile`文件稍微更改下即可,将使用`maven`进行构建的工作放到同一个文件中: + +```docker +FROM maven:3.6-alpine as BUILD + +COPY src /usr/app/src +COPY pom.xml /usr/app + +RUN mvn -f /usr/app/pom.xml clean package -Dmaven.test.skip=true + +FROM openjdk:8-jdk-alpine + +MAINTAINER cnych + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ENV TZ=Asia/Shanghai + +RUN mkdir /app + +WORKDIR /app + +COPY --from=BUILD /usr/app/target/polls-0.0.1-SNAPSHOT.jar /app/polls.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar","/app/polls.jar"] +``` + +前面课程中我们就讲解过 Docker 的多阶段构建,这里我们定义了两个阶段,第一个阶段利用`maven:3.6-alpine`这个基础镜像将我们的项目进行打包,然后将该阶段打包生成的`jar`包文件复制到第二阶段进行最后的镜像打包,这样就可以很好的完成我们的 Docker 镜像的构建工作。 + +第二种方式就是我们传统的方式,在 Jenkins Pipeline 中添加一个`maven`构建的阶段,然后在第二个 Docker 构建的阶段就可以直接获取到前面的`jar`包了,也可以很方便的完成镜像的构建工作,为了更加清楚的说明 Jenkins Pipeline 的用法,我们这里采用这种方式,所以 Dockerfile 文件还是使用第一个就行。 + +现在我们可以将服务端的代码推送到 Gitlab 上去,我们这里的仓库地址为:[http://git.qikqiak.com/course/polling-app-server.git](http://git.qikqiak.com/course/polling-app-server.git) + +> 注意,这里我们只推送的服务端代码。 + + +### 客户端 +客户端我们需要修改 API 的链接地址,修改文件`src/constants/index.js`中`API_BASE_URL`的地址,我们同样通过环境变量来进行区分,如果有环境变量`APISERVER_URL`,则优先使用这个环境变量来作为 API 请求的地址: + +```javascript +let API_URL = 'http://localhost:8080/api'; +if (process.env.APISERVER_URL) { + API_URL = `${process.env.APISERVER_URL}/api`; +} +export const API_BASE_URL = API_URL; +``` + +因为我们这里的项目使用的就是前后端分离的架构,所以我们同样需要将前端代码进行单独的部署,同样我们要将项目部署到 Kubernetes 环境中,所以也需要做容器化,同样在项目根目录下面添加一个`Dockerfile`文件: + +```docker +FROM nginx:1.15.10-alpine +ADD build /usr/share/nginx/html + +ADD nginx.conf +/etc/nginx/conf.d/default.conf +``` + +由于前端页面是单纯的静态页面,所以一般我们使用一个`nginx`镜像来运行,所以我们提供一个`nginx.conf`配置文件: +``` +server { + gzip on; + + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + location / { + try_files $uri /index.html; + expires 1h; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + +} +``` + +这里我们可以看到我们需要将前面页面打包到一个`build`目录,然后将改目录添加到 nginx 镜像中的`/usr/share/nginx/html`目录,这样当 nginx 镜像启动的时候就是直接使用的改文件夹下面的文件。 + +所以现在我们需要获取打包后的目录`build`,同样的,和上面服务端项目一样,我们可以使用两种方式来完成这个工作。 + +第一种方式自然是推荐的 Docker 的多阶段构建,我们在一个`node`镜像的环境中就可以打包我们的前端项目了,所以我们可以更改下`Dockerfile`文件,先进行 node 打包,然后再进行 nginx 启动: + +```docker +FROM node:alpine as BUILD + +WORKDIR /usr/src/app + +RUN mkdir -p /usr/src/app + +ADD . /usr/src/app + +RUN npm install && \ + npm run build + +FROM nginx:1.15.10-alpine +MAINTAINER cnych + +COPY --from=BUILD /usr/src/app/build /usr/share/nginx/html + +ADD nginx.conf +/etc/nginx/conf.d/default.conf +``` + +第二种方式和上面一样在 Jenkins Pipeline 中添加一个打包构建的阶段即可,我们这里采用这种方式,所以 Dockerfile 文件还是使用第一个就行。 + +现在我们可以将客户端的代码推送到 Gitlab 上去,我们这里的仓库地址为:[http://git.qikqiak.com/course/polling-app-client.git](http://git.qikqiak.com/course/polling-app-client.git) + + +## Jenkins +现在项目准备好了,接下来我们可以开始 Jenkins 的配置,还记得前面在 Pipeline 结合 Kubernetes 的课程中我们使用了一个`kubernetes`的 Jenkins 插件,但是之前使用的方式有一些不妥的地方,我们 Jenkins Pipeline 构建任务绑定到了一个固定的 Slave Pod 上面,这样就需要我们的 Slave Pod 中必须包含一系列构建所需要的依赖,比如 docker、maven、node、java 等等,这样就难免需要我们自己定义一个很庞大的 Slave 镜像,我们直接直接在 Pipeline 中去自定义 Slave Pod 中所需要用到的容器模板,这样我们需要什么镜像只需要在 Slave Pod Template 中声明即可,完全不需要去定义一个庞大的 Slave 镜像了。 + +首先去掉 Jenkins 中 kubernetes 插件中的 Pod Template 的定义,Jenkins -> 系统管理 -> 系统设置 -> 云 -> Kubernetes区域,删除下方的`Kubernetes Pod Template` -> 保存。 + +![jenkins kubernetes plugin](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/2iNvFm.jpg) + +然后新建一个名为`polling-app-server`类型为`流水线(Pipeline)`的任务: + +![new pipeline task](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/PVHJXH.jpg) + +然后在这里需要勾选`触发远程构建`的触发器,其中令牌我们可以随便写一个字符串,然后记住下面的 URL,将 JENKINS_URL 替换成 Jenkins 的地址,我们这里的地址就是:`http://jenkins.qikqiak.com/job/polling-app-server/build?token=server321` + +![trigger](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/pajeby.jpg) + + +然后在下面的`流水线`区域我们可以选择`Pipeline script`然后在下面测试流水线脚本,我们这里选择`Pipeline script from SCM`,意思就是从代码仓库中通过`Jenkinsfile`文件获取`Pipeline script`脚本定义,然后选择 SCM 来源为`Git`,在出现的列表中配置上仓库地址`http://git.qikqiak.com/course/polling-app-server.git`,由于我们是在一个 Slave Pod 中去进行构建,所以如果使用 SSH 的方式去访问 Gitlab 代码仓库的话就需要频繁的去更新 SSH-KEY,所以我们这里采用直接使用用户名和密码的形式来方式: + +![pipeline scm](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/ecnRly.jpg) + +在`Credentials`区域点击`添加`按钮添加我们访问 Gitlab 的用户名和密码: + +![gitlab auth](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/UwFxro.jpg) + +然后需要我们配置用于构建的分支,如果所有的分支我们都想要进行构建的话,只需要将`Branch Specifier`区域留空即可,一般情况下不同的环境对应的分支才需要构建,比如 master、develop、test 等,平时开发的 feature 或者 bugfix 的分支没必要频繁构建,我们这里就只配置 master 和 develop 两个分支用户构建: + +![gitlab branch config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/itdSlI.jpg) + + +然后前往 Gitlab 中配置项目`polling-app-server` Webhook,settings -> Integrations,填写上面得到的 trigger 地址: + +![webhook](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/aJTq7E.jpg) + +保存后,可以直接点击`Test` -> `Push Event`测试是否可以正常访问 Webhook 地址,这里需要注意的是我们需要配置下 Jenkins 的安全配置,否则这里的触发器没权限访问 Jenkins,系统管理 -> 全局安全配置:取消`防止跨站点请求伪造`,勾选上`匿名用户具有可读权限`: + +![security config](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/kaEQZ0.jpg) + +如果测试出现了`Hook executed successfully: HTTP 201`则证明 Webhook 配置成功了,否则就需要检查下 Jenkins 的安全配置是否正确了。 + +配置成功后我们只需要往 Gitlab 仓库推送代码就会触发 Pipeline 构建了。接下来我们直接在服务端代码仓库根目录下面添加`Jenkinsfile`文件,用于描述流水线构建流程。 + +首先定义最简单的流程,要注意这里和前面课程的不同之处,这里我们使用`podTemplate`来定义不同阶段使用的的容器,有哪些阶段呢? + +Clone 代码 -> 代码静态分析 -> 单元测试 -> Maven 打包 -> Docker 镜像构建/推送 -> Helm 更新服务。 + +Clone 代码在默认的 Slave 容器中即可;静态分析和单元测试我们这里直接忽略,有需要这个阶段的同学自己添加上即可;Maven 打包肯定就需要 Maven 的容器了;Docker 镜像构建/推送是不是就需要 Docker 环境了呀;最后的 Helm 更新服务是不是就需要一个有 Helm 的容器环境了,所以我们这里就可以很简单的定义`podTemplate`了,如下定义:(添加一个`kubectl`工具用于测试) + +```groovy +def label = "slave-${UUID.randomUUID().toString()}" + +podTemplate(label: label, containers: [ + containerTemplate(name: 'maven', image: 'maven:3.6-alpine', command: 'cat', ttyEnabled: true), + containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true), + containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true), + containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true) +], volumes: [ + hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'), + hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'), + hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock') +]) { + node(label) { + def myRepo = checkout scm + def gitCommit = myRepo.GIT_COMMIT + def gitBranch = myRepo.GIT_BRANCH + + stage('单元测试') { + echo "测试阶段" + } + stage('代码编译打包') { + container('maven') { + echo "代码编译打包阶段" + } + } + stage('构建 Docker 镜像') { + container('docker') { + echo "构建 Docker 镜像阶段" + } + } + stage('运行 Kubectl') { + container('kubectl') { + echo "查看 K8S 集群 Pod 列表" + sh "kubectl get pods" + } + } + stage('运行 Helm') { + container('helm') { + echo "查看 Helm Release 列表" + sh "helm list" + } + } + } +} +``` + +上面这段`groovy`脚本比较简单,我们需要注意的是`volumes`区域的定义,将容器中的`/root/.m2`目录挂载到宿主机上是为了给`Maven`构建添加缓存的,不然每次构建的时候都需要去重新下载依赖,这样就非常慢了;挂载`.kube`目录是为了能够让`kubectl`和`helm`两个工具可以读取到 Kubernetes 集群的连接信息,不然我们是没办法访问到集群的;最后挂载`/var/run/docker.sock`文件是为了能够让我们的`docker`这个容器获取到`Docker Daemon`的信息的,因为`docker`这个镜像里面只有客户端的二进制文件,我们需要使用宿主机的`Docker Daemon`来构建镜像,当然我们也需要在运行 Slave Pod 的节点上拥有访问集群的文件,然后在每个`Stage`阶段使用特定需要的容器来进行任务的描述即可,所以这几个`volumes`都是非常重要的 + +```groovy +volumes: [ + hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'), + hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'), + hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock') +] +``` + +另外一个值得注意的就是`label`标签的定义,我们这里使用 UUID 生成一个随机的字符串,这样可以让 Slave Pod 每次的名称都不一样,而且这样就不会被固定在一个 Pod 上面了,以后有多个构建任务的时候就不会存在等待的情况了,这和我们之前的课程中讲到的固定在一个 label 标签上有所不同。 + +然后我们将上面的`Jenkinsfile`文件提交到 Gitlab 代码仓库上: +```shell +$ git add Jenkinsfile +$ git commit -m "添加 Jenkinsfile 文件" +$ git push origin master +``` + +然后切换到 Jenkins 页面上,正常情况就可以看到我们的流水线任务`polling-app-server`已经被触发构建了,然后回到我们的 Kubernetes 集群中可以看到多了一个 slave 开头的 Pod,里面有5个容器,就是我们上面 podTemplate 中定义的4个容器,加上一个默认的 jenkins slave 容器,同样的,构建任务完成后,这个 Pod 也会被自动销毁掉: +```shell +$ kubectl get pods -n kube-ops +NAME READY STATUS RESTARTS AGE +jenkins-7fbfcc5ddc-xsqmt 1/1 Running 0 1d +slave-6e898009-62a2-4798-948f-9c80c3de419b-0jwml-6t6hb 5/5 Running 0 36s +...... +``` + +正常可以看到 Jenkins 中的任务构建成功了: + +![build successfully](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/DVIDeb.jpg) + + + +接下来的工作就是来实现上面具体的 Pipeline 脚本了。 + +## Pipeline +第一个阶段:单元测试,我们可以在这个阶段是运行一些单元测试或者静态代码分析的脚本,我们这里直接忽略。 + +第二个阶段:代码编译打包,我们可以看到我们是在一个`maven`的容器中来执行的,所以我们只需要在该容器中获取到代码,然后在代码目录下面执行 maven 打包命令即可,如下所示: +```groovy + stage('代码编译打包') { + try { + container('maven') { + echo "2. 代码编译打包阶段" + sh "mvn clean package -Dmaven.test.skip=true" + } + } catch (exc) { + println "构建失败 - ${currentBuild.fullDisplayName}" + throw(exc) + } + } +``` + +第三个阶段:构建 Docker 镜像,要构建 Docker 镜像,就需要提供镜像的名称和 tag,要推送到 Harbor 仓库,就需要提供登录的用户名和密码,所以我们这里使用到了`withCredentials`方法,在里面可以提供一个`credentialsId`为`dockerhub`的认证信息,如下: +```groovy +container('构建 Docker 镜像') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', + credentialsId: 'dockerhub', + usernameVariable: 'DOCKER_HUB_USER', + passwordVariable: 'DOCKER_HUB_PASSWORD']]) { + echo "3. 构建 Docker 镜像阶段" + sh """ + docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} + docker build -t ${image}:${imageTag} . + docker push ${image}:${imageTag} + """ + } +} +``` + +其中 ${image} 和 ${imageTag} 我们可以在上面定义成全局变量: +```groovy +def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim() +def dockerRegistryUrl = "registry.qikqiak.com" +def imageEndpoint = "course/polling-app-server" +def image = "${dockerRegistryUrl}/${imageEndpoint}" +``` + +docker 的用户名和密码信息则需要通过`凭据`来进行添加,进入 jenkins 首页 -> 左侧菜单`凭据` -> `添加凭据`,选择用户名和密码类型的,其中 ID 一定要和上面的`credentialsId`的值保持一致: + +![add docker hub credential](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/6uXfeq.jpg) + + +第四个阶段:运行 kubectl 工具,其实在我们当前使用的流水线中是用不到 kubectl 工具的,那么为什么我们这里要使用呢?这还不是因为我们暂时还没有去写应用的 Helm Chart 包吗?所以我们先去用原始的 YAML 文件来编写应用部署的资源清单文件,这也是我们写出 Chart 包前提,因为只有知道了应用如何部署才可能知道 Chart 包如何编写,所以我们先编写应用部署资源清单。 + +首先当然就是 Deployment 控制器了,如下所示:(k8s.yaml) +```yaml +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: polling-server + namespace: course + labels: + app: polling-server +spec: + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: polling-server + spec: + restartPolicy: Always + imagePullSecrets: + - name: myreg + containers: + - image: : + name: polling-server + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: api + env: + - name: DB_HOST + value: mysql + - name: DB_PORT + value: "3306" + - name: DB_NAME + value: polling_app + - name: DB_USER + value: polling + - name: DB_PASSWORD + value: polling321 + +--- + +kind: Service +apiVersion: v1 +metadata: + name: polling-server + namespace: course +spec: + selector: + app: polling-server + type: ClusterIP + ports: + - name: api-port + port: 8080 + targetPort: api + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: mysql + namespace: course +spec: + template: + metadata: + labels: + app: mysql + spec: + restartPolicy: Always + containers: + - name: mysql + image: mysql:5.7 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3306 + name: dbport + env: + - name: MYSQL_ROOT_PASSWORD + value: rootPassW0rd + - name: MYSQL_DATABASE + value: polling_app + - name: MYSQL_USER + value: polling + - name: MYSQL_PASSWORD + value: polling321 + volumeMounts: + - name: db + mountPath: /var/lib/mysql + volumes: + - name: db + hostPath: + path: /var/lib/mysql + +--- +kind: Service +apiVersion: v1 +metadata: + name: mysql + namespace: course +spec: + selector: + app: mysql + type: ClusterIP + ports: + - name: dbport + port: 3306 + targetPort: dbport +``` + +可以看到我们上面的 YAML 文件中添加使用的镜像是用标签代替的:`:`,这是因为我们的镜像地址是动态的,下依赖我们在上一个阶段打包出来的镜像地址的,所以我们这里用标签代替,然后将标签替换成真正的值即可,另外为了保证应用的稳定性,我们还在应用中添加了健康检查,所以需要在代码中添加一个健康检查的 Controller:(src/main/java/com/example/polls/controller/StatusController.java) +```java +package com.example.polls.controller; + +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/_status/healthz") +public class StatusController { + + @GetMapping + public String healthCheck() { + return "UP"; + } + +} +``` + +最后就是环境变量了,还记得前面我们更改了资源文件中数据库的配置吗?(src/main/resources/application.properties)因为要尽量通用,我们在部署应用的时候很有可能已经有一个外部的数据库服务了,所以这个时候通过环境变量传入进来即可。另外由于我们这里使用的是私有镜像仓库,所以需要在集群中提前创建一个对应的 Secret 对象: +```shell +$ kubectl create secret docker-registry myreg --docker-server=registry.qikqiak.com --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL --namespace course +``` + +在代码根目录下面创建一个 manifests 的目录,用来存放上面的资源清单文件,正常来说是不是我们只需要在镜像构建成功后,将上面的 k8s.yaml 文件中的镜像标签替换掉就 OK,所以这一步的动作如下: +```groovy +stage('运行 Kubectl') { + container('kubectl') { + echo "查看 K8S 集群 Pod 列表" + sh "kubectl get pods" + sh """ + sed -i "s//${image}" manifests/k8s.yaml + sed -i "s//${imageTag}" manifests/k8s.yaml + kubectl apply -f k8s.yaml + """ + } +} +``` + + +第五阶段:运行 Helm 工具,就是直接使用 Helm 来部署应用了,现在有了上面的基本的资源对象了,现在要创建 Chart 模板就相对容易了,首先确保我们本地安装有 Helm Client,使用下面的命令创建项目: +```shell +$ helm create polling-helm +Creating polling-helm +$ tree polling-helm +polling-helm +├── Chart.yaml +├── charts +├── templates +│   ├── NOTES.txt +│   ├── _helpers.tpl +│   ├── deployment.yaml +│   ├── ingress.yaml +│   └── service.yaml +└── values.yaml + +2 directories, 7 files +``` + +然后将上面的 k8s.yaml 文件我们拆分成四个文件放置到 templates 目录下面,把 polling-server 分成 deployment.yaml 和 service.yaml 文件,而数据库的我们单独创建一个文件夹,然后放置在文件夹下面: +```shell +$ tree polling-helm/templates +polling-helm/templates +├── NOTES.txt +├── _helpers.tpl +├── database +│   ├── database-svc.yaml +│   └── database-ss.yaml +├── deployment.yaml +├── ingress.yaml +└── service.yaml + +1 directory, 7 files +``` + +然后就可以定制模板了,首先针对主要的 deployment.yaml 文件,把所有能够定制的数据都通过 values 或者 template 来进行渲染: +```yaml +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: {{ include "polling-helm.fullname" . }} + labels: + app: {{ include "polling-helm.name" . }} + chart: {{ include "polling-helm.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: {{ include "polling-helm.name" . }} + release: {{ .Release.Name }} + spec: + restartPolicy: Always + imagePullSecrets: + - name: myreg + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 8080 + name: api + env: + - name: DB_HOST + value: {{ .Values.db.host }} + - name: DB_PORT + value: "3306" + - name: DB_NAME + value: polling_app + - name: DB_USER + value: polling + - name: DB_PASSWORD + value: polling321 + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} +``` + diff --git a/gitlab-ci-k8s-demo b/gitlab-ci-k8s-demo new file mode 160000 index 0000000..62ec25b --- /dev/null +++ b/gitlab-ci-k8s-demo @@ -0,0 +1 @@ +Subproject commit 62ec25b629f8a7c174c40916c38293694bcf6e5d -- GitLab