在 Kubernetes 中通过 Jenkins 和 Dynamic Slaves 实现 CI/CD
本文将介绍如何通过在 Kubernetes 集群中利用 Jenkins 和 Kubernetes-Jenkins-Plugin 实现动态按需扩展 jenkins-slave 实现 CI/CD。实验环境为阿里云 ACK Kubernetes 托管集群。
前提条件
已创建好的 Kubenretes 集群,包括自建集群和云服务提供商托管集群;
在已创建的 Kubenretes 集群中安装好的 Jenkins 服务;有关安装方式:
- 使用云服务提供商控制台安装:阿里云
- 使用 Helm Charts 进行安装,参见:Configuring CI/CD on Kubernetes with Jenkins – Medium, Jenkins Chart – Helm
- 使用 Docker Hub 镜像自行安装,参见:基于 jenkins 打造 kubernetes on aws 上的 CI/CD 管道 – AWS, jenkins/jenkins – Docker Hub
安装 Kubernetes 插件
以下进行的步骤依赖于 Jenkins 中 Kubernetes Plugin,需要在 Jenkins 设置内 Manage Plugins 页面自行安装。详情可参考 Jenkins Kubernetes Plugin。
配置 Kubernetes 集群信息
在全局设置中配置好 Kubernetes 集群的信息,进行连通性测试,由于本示例中针对 Jenkins 创建 Service Account 并赋予了权限,否则可能需要对 Credentials 进行配置。有关证书的配置生成:
- 在 Jenkins 的左侧导航栏中,选择系统管理。
- 在右侧的 Manage Jenkins 页面,单击系统设置。
- 在 Cloud 区域中,单击 Credentials 右侧的 Add。
- 将相应集群的 Kube Config 的信息填入对应的输入框中。
配置 Pod Template
这一步配置 Dynamic Slaves 的 Pod Template, 构建过程中 master 节点就会按照这个 Pod Template 通过请求 Kubernetes 集群的 API Server 拉起一个容器并挂载为 Jenkins Slave 节点用于构建,而 Pod Template 具体应该包含哪些 container 很大程度上取决于你的应用构建方式和部署方式,下面的示例主要围绕 Docker 镜像打包构建流程和部署到 Kubernetes 集群的部署方式展开。
查询各类资料的过程中,发现很多教程都会先自定义一个 jnlp-slave 的 container 用于和 master 主节点进行通信,而在本人实测的过程中发现,构建过程中会自动创建一个使用 jenkins/jnlp-slave:3.35-5-alpine
作为镜像的 jnlp-slave 的容器,即 Pod Template 只定义所需容器即可。需要注意的是大部分 alpine 版本的镜像会缺少部分常用的工具,例如实测过程中发现该镜像缺少 curl,导致钉钉推送出现问题,最后使用 wget 暂时解决。
在 Docker 镜像的构建方面,由于 Kubernetes 集群中缺少 Docker Daemon, 且考虑到 Docker 占用的空间较大,并没有采用打包带有 docker-ce 镜像的方式,而是使用了来自 Google 的 Kaniko Project,不依赖 Docker Daemon,可以直接运行在 Kubernetes 集群中。相关配置如下图:
值得注意的是,本示例中使用的镜像为 gcr.io/kaniko-project/executor:debug
,主要是因为在 tag latest 的镜像中缺少对 shell 脚本的支持,entrypoint 直接为 /kaniko/executor,故会对 pipeline 中执行一些脚本产生阻碍。至于国内的用户有个小技巧,可以使用 gcr.azk8s.cn/kaniko-project/executor:debug
避免被墙。
假设 Kaniko 编译镜像后需要推送到对应的镜像仓库,还需要配置对应仓库的权限,可以使用 Kubernetes 的 secret,对应的配置如下:
配置好 Kaniko 的容器模板后,接下来就需要配置部署容器了,实例中使用了自己打包的一个 Docker 镜像最为部署容器的镜像,以下是该镜像的 Dockerfile:
# 该镜像还可以用于在宿主机上配置 Docker in Docker 版本的 Jenkins
FROM jenkins/jenkins:centos
USER root
# https://get.helm.sh/helm-v3.1.2-linux-amd64.tar.gz
ENV ALICLI_VERSION aliyun-cli-linux-latest-amd64
ENV ALICLI_URL https://aliyuncli.alicdn.com/${ALICLI_VERSION}.tgz
ENV HELM_VERSION helm-v3.1.2
ENV HELM_PLATFORM linux-amd64
ENV HELM_URL https://get.helm.sh/${HELM_VERSION}-${HELM_PLATFORM}.tar.gz
ENV KUBECTL_VERSION v1.16.0
ENV KUBECTL_URL https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
WORKDIR /tmp
# 安装 Kubectl
RUN curl -LO ${KUBECTL_URL} && \
chmod +x ./kubectl && \
mv ./kubectl /usr/bin/kubectl
# 安装 Helm
RUN curl -O ${HELM_URL} && \
tar -xzvf ${HELM_VERSION}-${HELM_PLATFORM}.tar.gz && \
chmod +x ./${HELM_PLATFORM}/helm && \
mv ./${HELM_PLATFORM}/helm /usr/bin/helm && \
rm -rf ${HELM_VERSION}-${HELM_PLATFORM}.tar.gz && rm -rf ./${HELM_PLATFORM}
# 安装 Aliyun-Cli
RUN curl -O ${ALICLI_URL} && \
tar -xzvf ${ALICLI_VERSION}.tgz && \
chmod +x ./aliyun && mv ./aliyun /usr/bin/aliyun && \
rm -rf ${ALICLI_VERSION}.tgz
COPY ./config /home/.kube/config
ENV KUBECONFIG /home/.kube/config
USER jenkins
Jenkins 中部署容器配置如下:
配置好相应容器的模板后,还有较多的自定义参数可供配置,例如容器的资源限制,构建 Pod 留存策略和留存时间等,可以按需进行配置:
流水线配置
流水线的基本配置可以参考相关资料,此处给出供参考的 Jenkinsfile:
#!/bin/env groovy
pipeline {
agent {
node {
label 'devops-jenkins-build-agent' //
}
}
stages {
stage('初始化') {
steps {
echo '初始化'
sh "git branch: 'test', credentialsId: '', url: 'https://github.com/xxx/jenkins-demo.git'"
}
}
stage('构建') {
environment {
VERSION=sh(returnStdout: true, script:"sh scripts/get-version.sh").trim()
IMAGE_REPOSITORY=sh(returnStdout: true, script:"sh scripts/get-backend-image-url.sh").trim()
}
steps {
container(name: "kaniko", shell: '/busybox/sh') {
sh "/kaniko/executor -f ./Dockerfile -c `pwd` --destination=${IMAGE_REPOSITORY}:${VERSION} --skip-tls-verify"
}
}
}
stage('部署') {
steps {
container('kubectl') {
sh "kubectl apply -f ./test.yaml"
}
}
}
}
post {
always {
echo '构建结束!!!!!'
}
success {
echo '恭喜您,构建成功!!!'
sh '''
AUTHOR="`git log -1 --pretty=format:'%an'`"
PROJECT="test"
ENV="`sh scripts/get-env.sh`"
MODULE="backend"
VERSION="`bash scripts/get-version.sh`"
RAW_BODY="`git log -1 --pretty=format:'%B'`"
STATUS="恭喜您,发布成功!!!"
sh scripts/notice-dinding.sh "$AUTHOR" "$PROJECT" "$ENV" "$MODULE" "$VERSION" "$RAW_BODY" "$STATUS"
'''
}
failure {
sh '''
AUTHOR="`git log -1 --pretty=format:'%an'`"
PROJECT="test"
ENV="`sh scripts/get-env.sh`"
MODULE="backend"
VERSION="`bash scripts/get-version.sh`"
RAW_BODY="`git log -1 --pretty=format:'%B'`"
STATUS="抱歉,发布失败!!!"
sh scripts/notice-dinding.sh "$AUTHOR" "$PROJECT" "$ENV" "$MODULE" "$VERSION" "$RAW_BODY" "$STATUS"
'''
}
unstable {
echo '该任务已经被标记为不稳定任务!!!'
}
cleanup {
echo '清理环境!!!'
sh "rm -rf ./*"
}
}
}