Jenkins实现Kubernetes部署流水线

在Agent定制环境准备好后,我们将构建完整的部署流水线。

根据我们选用的技术栈,部署流水线划分为如下阶段:

  1. checkout代码

  2. gradle编译

  3. 构建Docker镜像、推送到镜像服务器

  4. 发布到Kubernetes中

在开始构建流水线前,我们还需要做一些准备工作。

准备工作

首先,我们需要创建一个新的Spring Boot项目homs-start,用于流水线的演示。

这里使用Sping Boot Starter直接生成的,代码放到了Gitee托管,参考这里

第二步,我们需要修改Jenskin的项目名,从test修改为homs-start。

接下来,我们需要在Jenkins上配置Gitee的ssh key凭据。

  1. 先确认已在Gitee上配置了公钥,并且保留了对应的私钥,参考这篇教程

  2. 在Jenkins上配置Gitee的凭据,路径是:Jenkins -> Manage Jenkins -> Manage Credentials -> Global

  3. SSH Username with private key,填入gitee的用户名和对应私钥,命名为GITEE

f

在流水线的步骤3中,我们需要打包一个新的镜像。

如果你还记得前两节的内容,应该知道我们的Agent实际是运行在Docker中的。

因此,我们的Agent需要具有”Docker Inside Docker”的能力,一般常见的有三种方法,可以参考这篇文章)。

本文中,我们选用socks挂载的模式,对Agent的镜像做一些改造,如下:

  1. FROM jenkins/inbound-agent:latest-jdk8
  2. ENV GRADLE_VERSION=7.2
  3. ENV K8S_VERSION=v1.22.3
  4. ENV DOCKER_CHANNEL stable
  5. ENV DOCKER_VERSION 18.06.3-ce
  6. # tool
  7. USER root
  8. RUN apt-get update && \
  9. apt-get install -y curl unzip sudo && \
  10. apt-get clean
  11. # docker
  12. RUN curl -fsSL "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/x86_64/docker-${DOCKER_VERSION}.tgz" \
  13. | tar -xzC /usr/local/bin --strip=1 docker/docker
  14. # gradle
  15. RUN curl -skL -o /tmp/gradle-bin.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip && \
  16. mkdir -p /opt/gradle && \
  17. unzip -q /tmp/gradle-bin.zip -d /opt/gradle && \
  18. ln -sf /opt/gradle/gradle-$GRADLE_VERSION/bin/gradle /usr/local/bin/gradle
  19. RUN chown -R 1001:0 /opt/gradle && \
  20. chmod -R g+rw /opt/gradle
  21. # kubectl
  22. RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$K8S_VERSION/bin/linux/amd64/kubectl
  23. RUN chmod +x ./kubectl
  24. RUN mv ./kubectl /usr/local/bin
  25. # add jenkins user to sudoer without password
  26. RUN usermod -aG sudo jenkins
  27. RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
  28. USER jenkins

如上所述,我们对构建镜像的改动如下:

  • 增加了docker二进制文件

  • 对用户jenkins添加了sudo免密权限

运行脚本也需要做一些改造:

  1. #!/bin/bash
  2. NAME="jenkins_e1"
  3. PUID="1000"
  4. PGID="1000"
  5. docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
  6. docker run \
  7. --name $NAME \
  8. --env PUID=$PUID \
  9. --env PGID=$PGID \
  10. --add-host kubernetes:10.1.172.136 \
  11. --volume /var/run/docker.sock:/var/run/docker.sock \
  12. --detach \
  13. --init coder4/jenkins-my-agent \
  14. -workDir=/home/jenkins/agent \
  15. -url http://10.1.172.136:8080 \
  16. b057970bf978f53a8f945d470ac644e44c945e4b7259b216f703dedb95d0cac9 \
  17. e1

运行脚本的主要是,挂载了/var/run/docker.sock到容器内。

运行后,我们以默认用户登录到容器内,查看docker是否可以正常使用:

  1. jenkins@936e27b3c460:~$ sudo docker ps
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. 936e27b3c460 coder4/jenkins-my-agent "/usr/local/bin/jenk…" 6 seconds ago Up 4 seconds jenkins_e1
  4. 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
  5. 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。

构建脚本

下面,我们按照流水线的步骤,构建脚本如下:

  1. pipeline {
  2. agent any
  3. environment {
  4. project = "coder4/homs-start"
  5. }
  6. stages {
  7. stage('git') {
  8. steps {
  9. git credentialsId: 'GITEE', url: 'git@gitee.com:/'+ project + '.git', branch: 'master'
  10. }
  11. }
  12. stage('gradle') {
  13. steps {
  14. sh "gradle build"
  15. }
  16. }
  17. stage('docker image build') {
  18. steps {
  19. sh '''
  20. # get right jar
  21. jarPath=$(du -a ./build/libs/* | sort -n -r | head -n 1 | cut -f2-)
  22. jarFile=$( echo ${jarPath##*/} )
  23. # make Dockerfile
  24. cat <<EOF > Dockerfile
  25. FROM openjdk:8
  26. COPY $jarPath $jarFile
  27. ENTRYPOINT ["java","-jar","/$jarFile"]
  28. EOF
  29. # build Docker image
  30. sudo docker build -t coder4/${JOB_NAME}:${BUILD_NUMBER} .
  31. # push to docker hub
  32. sudo docker push coder4/${JOB_NAME}:${BUILD_NUMBER}
  33. '''
  34. }
  35. }
  36. stage('k8s') {
  37. steps {
  38. withKubeConfig([credentialsId: "60a8e9d2-0212-4ff4-aa98-f46fced97121",serverUrl: "https://kubernetes:6443"]) {
  39. sh "kubectl create deployment my-nginx --image=coder4/${JOB_NAME}:${BUILD_NUMBER}"
  40. }
  41. }
  42. }
  43. }
  44. }

脚本比较长,我们分步解析:

  1. git拉代码

    1. 这里直接使用的gitee的公开仓库,可以根据实际情况,替换为公司内的gitlab等私有仓库

    2. GITEE的凭据,就是在准备工作中配置的那个

  2. gradle编译

    1. 这里直接使用gradle build命令

    2. 编译好后,会在build/libs目录下,生成jar包

  3. 打包Docker镜像,上传镜像

    1. 首先选择build/libs下尺寸最大的jar包(一般是fat jar,可独立运行的那个)

    2. 基于openjdk8的基础镜像,添加打好的jar包,并设定启动为jar包

    3. 构建好镜像后,将其推送到镜像仓库。这里选用了Docker Hub共有仓库,你可以换用Harbor等私有仓库。

    4. 这里默认使用项目名做为镜像名,构建版本做为镜像版本号

  4. 在Kubernetes上部署

    1. 使用上面的镜像,创建一个deployment

保存上述JenkinsFile脚本后,点击部署,如果一切顺利,会部署成功,我们看一下部署结果:

  1. kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. homs-start795f967dd6-7szxp 1/1 Running 0 57s

查看日志:

  1. kubectl logs -f my-nginx-795f967dd6-7szxp
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.5.6)
  9. 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 /)
  10. 2021-11-10 02:49:45.473 INFO 1 --- [ main] com.homs.start.StartApplication : No active profile set, falling back to default profiles: default
  11. 2021-11-10 02:49:46.866 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
  12. 2021-11-10 02:49:46.887 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
  13. 2021-11-10 02:49:46.887 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.54]
  14. 2021-11-10 02:49:46.999 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
  15. 2021-11-10 02:49:47.000 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1450 ms
  16. 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 ''
  17. 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进程已成功启动!

至此,我们已经完整地实现了全链路的部署流水线开发。

同时,上述流水线还有很大的改进空间,我们将在下一节继续优化流水线。