Jenkins实现Kubernetes部署流水线
在Agent定制环境准备好后,我们将构建完整的部署流水线。
根据我们选用的技术栈,部署流水线划分为如下阶段:
-
checkout代码
-
gradle编译
-
构建Docker镜像、推送到镜像服务器
-
发布到Kubernetes中
在开始构建流水线前,我们还需要做一些准备工作。
准备工作
首先,我们需要创建一个新的Spring Boot项目homs-start,用于流水线的演示。
这里使用Sping Boot Starter直接生成的,代码放到了Gitee托管,参考这里。
第二步,我们需要修改Jenskin的项目名,从test修改为homs-start。
接下来,我们需要在Jenkins上配置Gitee的ssh key凭据。
-
先确认已在Gitee上配置了公钥,并且保留了对应的私钥,参考这篇教程。
-
在Jenkins上配置Gitee的凭据,路径是:Jenkins -> Manage Jenkins -> Manage Credentials -> Global
-
SSH Username with private key,填入gitee的用户名和对应私钥,命名为GITEE
在流水线的步骤3中,我们需要打包一个新的镜像。
如果你还记得前两节的内容,应该知道我们的Agent实际是运行在Docker中的。
因此,我们的Agent需要具有"Docker Inside Docker"的能力,一般常见的有三种方法,可以参考[这篇文章](如何在Docker容器中运行Docker [3种方法] - 云+社区 - 腾讯云)。
本文中,我们选用socks挂载的模式,对Agent的镜像做一些改造,如下:
FROM jenkins/inbound-agent:latest-jdk8
ENV GRADLE_VERSION=7.2
ENV K8S_VERSION=v1.22.3
ENV DOCKER_CHANNEL stable
ENV DOCKER_VERSION 18.06.3-ce
# tool
USER root
RUN apt-get update && \
apt-get install -y curl unzip sudo && \
apt-get clean
# docker
RUN curl -fsSL "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/x86_64/docker-${DOCKER_VERSION}.tgz" \
| tar -xzC /usr/local/bin --strip=1 docker/docker
# gradle
RUN curl -skL -o /tmp/gradle-bin.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip && \
mkdir -p /opt/gradle && \
unzip -q /tmp/gradle-bin.zip -d /opt/gradle && \
ln -sf /opt/gradle/gradle-$GRADLE_VERSION/bin/gradle /usr/local/bin/gradle
RUN chown -R 1001:0 /opt/gradle && \
chmod -R g+rw /opt/gradle
# kubectl
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$K8S_VERSION/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/local/bin
# add jenkins user to sudoer without password
RUN usermod -aG sudo jenkins
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jenkins
如上所述,我们对构建镜像的改动如下:
-
增加了docker二进制文件
-
对用户jenkins添加了sudo免密权限
运行脚本也需要做一些改造:
#!/bin/bash
NAME="jenkins_e1"
PUID="1000"
PGID="1000"
docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
docker run \
--name $NAME \
--env PUID=$PUID \
--env PGID=$PGID \
--add-host kubernetes:10.1.172.136 \
--volume /var/run/docker.sock:/var/run/docker.sock \
--detach \
--init coder4/jenkins-my-agent \
-workDir=/home/jenkins/agent \
-url http://10.1.172.136:8080 \
b057970bf978f53a8f945d470ac644e44c945e4b7259b216f703dedb95d0cac9 \
e1
运行脚本的主要是,挂载了/var/run/docker.sock到容器内。
运行后,我们以默认用户登录到容器内,查看docker是否可以正常使用:
jenkins@936e27b3c460:~$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
936e27b3c460 coder4/jenkins-my-agent "/usr/local/bin/jenk…" 6 seconds ago Up 4 seconds jenkins_e1
577db2106c7d jenkins/jenkins:lts-jdk11 "/sbin/tini -- /usr/…" 4 days ago Up About an hour 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp jenkins
d44c3e421fb7 registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.25 "/usr/local/bin/entr…" 5 days ago Up About an hour 127.0.0.1:50437->22/tcp, 127.0.0.1:50440->2376/tcp, 127.0.0.1:50442->5000/tcp, 127.0.0.1:50443->8443/tcp, 127.0.0.1:50441->32443/tcp minikube
注意,因为挂载的socks默认是root权限,这里需要使用sudo。
构建脚本
下面,我们按照流水线的步骤,构建脚本如下:
pipeline {
agent any
environment {
project = "coder4/homs-start"
}
stages {
stage('git') {
steps {
git credentialsId: 'GITEE', url: 'git@gitee.com:/'+ project + '.git', branch: 'master'
}
}
stage('gradle') {
steps {
sh "gradle build"
}
}
stage('docker image build') {
steps {
sh '''
# get right jar
jarPath=$(du -a ./build/libs/* | sort -n -r | head -n 1 | cut -f2-)
jarFile=$( echo ${jarPath##*/} )
# make Dockerfile
cat <<EOF > Dockerfile
FROM openjdk:8
COPY $jarPath $jarFile
ENTRYPOINT ["java","-jar","/$jarFile"]
EOF
# build Docker image
sudo docker build -t coder4/${JOB_NAME}:${BUILD_NUMBER} .
# push to docker hub
sudo docker push coder4/${JOB_NAME}:${BUILD_NUMBER}
'''
}
}
stage('k8s') {
steps {
withKubeConfig([credentialsId: "60a8e9d2-0212-4ff4-aa98-f46fced97121",serverUrl: "https://kubernetes:6443"]) {
sh "kubectl create deployment my-nginx --image=coder4/${JOB_NAME}:${BUILD_NUMBER}"
}
}
}
}
}
脚本比较长,我们分步解析:
-
git拉代码
-
这里直接使用的gitee的公开仓库,可以根据实际情况,替换为公司内的gitlab等私有仓库
-
GITEE的凭据,就是在准备工作中配置的那个
-
-
gradle编译
-
这里直接使用gradle build命令
-
编译好后,会在build/libs目录下,生成jar包
-
-
打包Docker镜像,上传镜像
-
首先选择build/libs下尺寸最大的jar包(一般是fat jar,可独立运行的那个)
-
基于openjdk8的基础镜像,添加打好的jar包,并设定启动为jar包
-
构建好镜像后,将其推送到镜像仓库。这里选用了Docker Hub共有仓库,你可以换用Harbor等私有仓库。
-
这里默认使用项目名做为镜像名,构建版本做为镜像版本号
-
-
在Kubernetes上部署
- 使用上面的镜像,创建一个deployment
保存上述JenkinsFile脚本后,点击部署,如果一切顺利,会部署成功,我们看一下部署结果:
kubectl get pods
NAME READY STATUS RESTARTS AGE
homs-start795f967dd6-7szxp 1/1 Running 0 57s
查看日志:
kubectl logs -f my-nginx-795f967dd6-7szxp
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.6)
2021-11-10 02:49:45.469 INFO 1 --- [ main] com.homs.start.StartApplication : Starting StartApplication using Java 1.8.0_312 on my-nginx-795f967dd6-7szxp with PID 1 (/homs-start-0.0.1-SNAPSHOT.jar started by root in /)
2021-11-10 02:49:45.473 INFO 1 --- [ main] com.homs.start.StartApplication : No active profile set, falling back to default profiles: default
2021-11-10 02:49:46.866 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-11-10 02:49:46.887 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-11-10 02:49:46.887 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.54]
2021-11-10 02:49:46.999 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-11-10 02:49:47.000 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1450 ms
2021-11-10 02:49:47.964 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-11-10 02:49:47.974 INFO 1 --- [ main] com.homs.start.StartApplication : Started StartApplication in 3.119 seconds (JVM running for 5.216)
如上所示,Pod中的Spring Boot进程已成功启动!
至此,我们已经完整地实现了全链路的部署流水线开发。
同时,上述流水线还有很大的改进空间,我们将在下一节继续优化流水线。