一个简单的jenkins+harbor+k8s的cicd

# Kubernetes CI/CD 环境搭建指南

由于学习K8S有一段时间了,之前使用Jenkins Pipeline实现的CI/CD也已经很久没有更新。现在想尝试一些新的东西。

## 网络配置概览

| IP地址      | 主机名         | 备注       |
|-------------|----------------|------------|
| 172.31.7.11 | master1-7-11   | 主Master   |
| 172.31.7.21 | jenkins-7-21   | Jenkins    |
| 172.31.7.22 | gitlab-7-22    | GitLab     |
| 172.31.7.10 | harbor-7-10    | Harbor     |

备注:系统均为Ubuntu

---

# 一、准备Jenkins环境(特别是JDK)

### 安装JDK

```bash
apt install openjdk-17-jdk 

下载最新稳定版Jenkins安装包

下载页面
选择版本

安装长期支持版本Jenkins

sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
  https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install jenkins

修改Jenkins配置文件 /etc/default/jenkins

vim /etc/default/jenkins
NAME=root
JAVA_ARGS="-Djava.awt.headless=true"
JENKINS_USER=root
JENKINS_GROUP=root
JENKINS_HOME=/var/lib/$NAME

修改Jenkins systemd服务文件 /usr/lib/systemd/system/jenkins.service

vim /usr/lib/systemd/system/jenkins.service
User=root # 将服务启动的用户改为root
Group=root # 服务启动的组改为root

重启Jenkins服务:

systemctl restart jenkins
systemctl daemon-reload
Jenkins Web界面

二、安装GitLab

下载GitLab安装包

清华大学镜像站下载喜欢的版本。

配置并启动GitLab

参考这篇博客进行安装与配置。

创建用户并修改密码,创建测试项目

GitLab用户管理

### 1. **Kubernetes Deployment YAML 文件**

这个YAML文件定义了一个Kubernetes部署(Deployment)和服务(Service),用于在Kubernetes集群中运行一个Tomcat应用。

```yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    app: item1-tomcat-app1-deployment-label
  name: item1-tomcat-app1-deployment
  namespace: item1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: item1-tomcat-app1-selector
  template:
    metadata:
      labels:
        app: item1-tomcat-app1-selector
    spec:
      imagePullSecrets:
      - name: harbor-secret
      containers:
      - name: item1-tomcat-app1-container
        image: harbor.xtec.com/item1/tomcat:8.5.43_20250117_013112
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          protocol: TCP
          name: http
        env:
        - name: "password"
          value: "123456"
        - name: "age"
          value: "18"
        resources:
          limits:
            cpu: 1
            memory: "512Mi"
          requests:
            cpu: 500m
            memory: "512Mi"
        volumeMounts:
        - name: item1-images
          mountPath: /usr/local/nginx/html/webapp/images
          readOnly: false
        - name: item1-static
          mountPath: /usr/local/nginx/html/webapp/static
          readOnly: false
      volumes:
      - name: item1-images
        nfs:
          server: 172.31.7.20
          path: /data/k8sdata/item1/images
      - name: item1-static
        nfs:
          server: 172.31.7.20
          path: /data/k8sdata/item1/static

---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: item1-tomcat-app1-service-label
  name: item1-tomcat-app1-service
  namespace: item1
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 30092
  selector:
    app: item1-tomcat-app1-selector

功能说明

  • 定义了一个名为 item1-tomcat-app1-deployment 的部署,使用两个副本(replicas: 2)。
  • 每个容器使用 harbor.xtec.com/item1/tomcat:8.5.43_20250117_013112 镜像,并暴露8080端口。
  • 使用NFS挂载卷来存储静态资源。
  • 定义了一个名为 item1-tomcat-app1-service 的服务,类型为 NodePort,将集群内部的8080端口映射到节点上的30092端口。

2. Dockerfile

这个Dockerfile基于Harbor镜像仓库中的基础Tomcat镜像构建一个新的Tomcat应用镜像。

FROM harbor.xtec.com/pub-images/tomcat:8.5.43_20250107_235050

# 创建并设置 Tomcat 应用目录
RUN mkdir -p /data/tomcat/webapps/myapp && \
    chown -R tomcat:tomcat /data/tomcat/webapps/myapp

# 清空默认应用目录并复制文件
COPY catalina.sh /apps/tomcat/bin/catalina.sh
COPY server.xml /apps/tomcat/conf/server.xml
COPY app1.tar.gz /data/tomcat/webapps/myapp/app1.tar.gz
COPY run_tomcat.sh /apps/tomcat/bin/run_tomcat.sh

# 解压应用包并设置权限
RUN tar -xzf /data/tomcat/webapps/myapp/app1.tar.gz -C /data/tomcat/webapps/myapp/ && \
    rm /data/tomcat/webapps/myapp/app1.tar.gz && \
    chown -R tomcat:tomcat /data/ /apps/ && \
    chmod +x /apps/tomcat/bin/catalina.sh /apps/tomcat/bin/run_tomcat.sh

# 清理 Tomcat 工作目录
RUN rm -rf /apps/tomcat/work/Catalina/localhost/*

# 健康检查
HEALTHCHECK CMD curl -f http://localhost:8080 || exit 1

# 暴露端口
EXPOSE 8080 8443

# 启动命令
CMD ["/apps/tomcat/bin/run_tomcat.sh"]

功能说明

  • 基于基础Tomcat镜像创建新的镜像。
  • 设置应用目录并复制必要的配置文件和应用程序包。
  • 解压缩应用程序包并设置正确的权限。
  • 清理Tomcat的工作目录以确保干净启动。
  • 添加健康检查以确保Tomcat正常运行。
  • 暴露8080和8443端口,并使用自定义脚本启动Tomcat。

3. 镜像构建脚本 (build-command.sh)

这个脚本用于构建和推送Docker镜像到Harbor镜像仓库。

#!/bin/bash
# build-command.sh - 构建和推送 Docker 镜像脚本
# Author: orochw
# Updated: 2025-01-16

HARBOR="harbor.xtec.com"
REPO="item1"
IMAGE="tomcat"
FIXED_TAG="8.5.43"
DOCKERFILE_DIR="/opt/k8s-data/dockerfile/web/item1/tomcat-app1"

# 确保接收时间戳参数
if [ -z "$1" ]; then
  echo "ERROR: Date timestamp is missing!"
  exit 1
fi
DATE=$1
FULL_IMAGE="$HARBOR/$REPO/$IMAGE:${FIXED_TAG}_$DATE"

log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}

ERROR_exit() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1"
  exit 1
}

# 检查 Docker 是否可用
docker -v >/dev/null 2>&1 || ERROR_exit "Docker 未安装或不可用,请检查环境!"

# 构建 Docker 镜像
log "开始构建 Docker 镜像: $FULL_IMAGE"
docker build -t $FULL_IMAGE $DOCKERFILE_DIR || ERROR_exit "Docker 构建失败!"

# 推送 Docker 镜像
log "开始推送 Docker 镜像: $FULL_IMAGE"
docker push $FULL_IMAGE || ERROR_exit "Docker 推送失败!"

log "Docker 镜像构建和推送完成: $FULL_IMAGE"

功能说明

  • 确保传递了时间戳参数,并生成完整的镜像名称。
  • 检查Docker是否可用。
  • 构建并推送镜像到指定的Harbor仓库。

4. CI/CD 脚本

这个脚本实现了从代码克隆、打包、传输、镜像构建、更新Kubernetes配置到应用更改的整个CI/CD流程。

#!/bin/bash

# 配置部分
HARBOR="harbor.xtec.com"
REPO="item1"
IMAGE="tomcat"
FIXED_TAG="8.5.43"
K8S_CONTROLLER="172.31.7.11"
GIT_URL="git@172.31.7.22:item1/app1.git"
SRC_DIR="/data/gitdata/item1/app1"
DEST_DIR="/opt/k8s-data/dockerfile/web/item1/tomcat-app1"
K8S_YAML="/opt/k8s-data/yaml/item1/tomcat-app1/tomcat-app1.yaml"

# 记录开始时间
starttime=$(date +'%Y-%m-%d %H:%M:%S')

# 函数定义
log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}

ERROR_exit() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1"
  exit 1
}

code_clone() {
  log "开始拉取代码分支: $BRANCH"
  mkdir -p $(dirname $SRC_DIR)
  rm -rf $SRC_DIR
  git clone -b $BRANCH $GIT_URL $SRC_DIR || ERROR_exit "Git 克隆失败!"
  log "代码拉取完成"
}

package_file() {
  log "开始打包代码文件"
  mkdir -p $DEST_DIR
  tar -czf $DEST_DIR/app1.tar.gz -C $SRC_DIR . || ERROR_exit "文件打包失败!"
  log "文件打包完成: $DEST_DIR/app1.tar.gz"
}

copy_file() {
  log "开始复制文件到 K8S 控制器: $K8S_CONTROLLER"
  ssh root@$K8S_CONTROLLER "mkdir -p $DEST_DIR"
  scp -r $DEST_DIR/* root@$K8S_CONTROLLER:$DEST_DIR || ERROR_exit "文件复制失败!"
  log "文件成功复制到 $K8S_CONTROLLER:$DEST_DIR"
}

build_image() {
  log "开始构建和推送 Docker 镜像: $FULL_IMAGE"
  ssh root@$K8S_CONTROLLER <<EOF
    cd $DEST_DIR
    chmod +x build-command.sh
    ./build-command.sh $DATE || exit 1
EOF
  [ $? -ne 0 ] && ERROR_exit "Docker 镜像构建或推送失败!"
  log "Docker 镜像构建并推送完成: $FULL_IMAGE"
}

update_k8s_yaml() {
  log "开始更新 Kubernetes YAML 文件"
  ssh root@$K8S_CONTROLLER <<EOF
    if [ -f $K8S_YAML ]; then
      sed -i "s|image: $HARBOR/$REPO/$IMAGE:.*|image: $FULL_IMAGE|g" $K8S_YAML
      if ! grep -q 'imagePullSecrets' $K8S_YAML; then
        sed -i '/containers:/i \      imagePullSecrets:\n      - name: harbor-secret' $K8S_YAML
      fi
    else
      echo "ERROR: Kubernetes YAML 文件不存在: $K8S_YAML"
      exit 1
    fi
EOF
  [ $? -ne 0 ] && ERROR_exit "Kubernetes YAML 文件更新失败!"
  log "Kubernetes YAML 文件更新完成"
}

apply_k8s_changes() {
  log "应用 Kubernetes 部署更新"
  ssh root@$K8S_CONTROLLER "kubectl apply -f $K8S_YAML" || ERROR_exit "Kubernetes 部署更新失败!"
  log "Kubernetes 部署更新完成"
}

rollback_last_version() {
  log "开始回滚到上一个版本"
  ssh root@$K8S_CONTROLLER "kubectl rollout undo deployment/item1-tomcat-app1-deployment -n item1" || ERROR_exit "回滚失败!"
  log "回滚成功"
}

# 显示用法
usage() {
  echo "Usage: $0 {deploy|rollback_last_version} [branch]"
}

# 参数处理
OPERATION=${1:-deploy}
BRANCH=${2:-main} # 默认分支为 main
DATE=$(date +%Y%m%d_%H%M%S)
FULL_IMAGE="$HARBOR/$REPO/$IMAGE:${FIXED_TAG}_$DATE"

# 主逻辑
case $OPERATION in
  deploy)
    code_clone
    package_file
    copy_file
    build_image
    update_k8s_yaml
    apply_k8s_changes
    ;;
  rollback_last_version)
    rollback_last_version
    ;;
  *)
    usage
    exit 1
    ;;
esac

# 显示总耗时
endtime=$(date +'%Y-%m-%d %H:%M:%S')
start_seconds=$(date --date="$starttime" +%s)
end_seconds=$(date --date="$endtime" +%s)
log "总执行时间: $((end_seconds - start_seconds)) 秒"

功能说明

  • code_clone: 从指定Git仓库拉取特定分支的代码。
  • package_file: 将代码打包成.tar.gz格式。
  • copy_file: 将打包好的文件复制到Kubernetes控制器节点。
  • build_image: 在Kubernetes控制器上构建并推送Docker镜像。
  • update_k8s_yaml: 更新Kubernetes YAML文件中的镜像地址,并添加imagePullSecrets部分。
  • apply_k8s_changes: 应用更新后的YAML文件到Kubernetes集群。
  • rollback_last_version: 回滚到上一个版本。
  • 主逻辑: 根据传入的操作类型(deployrollback_last_version)执行相应的操作。

脚本中的函数及其作用

以下是脚本中定义的主要函数及其作用:

  1. log():

    • 作用: 打印带时间戳的日志信息,便于调试和追踪脚本执行过程。
    • 示例调用:
      log "开始构建 Docker 镜像: $FULL_IMAGE"
      
  2. ERROR_exit():

    • 作用: 当发生错误时打印错误信息并退出脚本执行,确保问题不会被忽略。
    • 示例调用:
      ERROR_exit "Docker 构建失败!"
      
  3. code_clone():

    • 作用: 从指定Git仓库克隆代码到本地目录,以便后续步骤可以访问这些代码进行打包和构建。
    • 示例调用:
      code_clone
      
  4. package_file():

    • 作用: 将克隆下来的代码打包成压缩文件(如.tar.gz),以便传输到Kubernetes控制器节点。
    • 示例调用:
      package_file
      
  5. copy_file():

    • 作用: 使用scp命令将打包好的文件复制到Kubernetes控制器节点上的指定目录。
    • 示例调用:
      copy_file
      
  6. build_image():

    • 作用: 在Kubernetes控制器节点上运行build-command.sh脚本,实际执行镜像的构建和推送操作。
    • 示例调用:
      build_image
      
  7. update_k8s_yaml():

    • 作用: 更新Kubernetes YAML配置文件,确保其引用的是最新构建的镜像。
    • 示例调用:
      update_k8s_yaml
      
  8. apply_k8s_changes():

    • 作用: 应用更新后的Kubernetes YAML配置文件,触发集群内的部署更新。
    • 示例调用:
      apply_k8s_changes
      
  9. rollback_last_version():

    • 作用: 回滚到前一个稳定版本,使用Kubernetes的rollout undo命令实现。
    • 示例调用:
      rollback_last_version
      

这些函数共同构成了一个完整的自动化流程,使得从代码提交到生产环境部署变得更加高效且可管理。

四、创建Jenkins任务

1. 安装SSH插件及配置凭据

插件安装

  • 步骤:在Jenkins中安装SSH插件。
  • 截图插件安装

远程主机设置

  • 创建新的凭据
    • 登录Jenkins -> 系统管理 -> 凭据 -> 全局 -> 新增凭据
    • 结果如图:远程主机设置
  • 系统管理 -> 系统 -> SSH Remote Hosts
    • 设置如图:SSH远程主机

2. 创建任务

  • 新建任务:选择“自由风格项目”类型。
    • 截图:创建任务
    • 配置Git仓库地址和分支。
    • 构建触发器:可以设置定时构建、轮询SCM等触发条件。
    • 构建环境:如果需要,可以设置环境变量等。
    • 构建:添加执行Shell脚本的步骤,调用前面准备好的CI/CD脚本进行构建、打包、推送镜像以及更新Kubernetes YAML文件等操作。

五、测试CI/CD流程

更新测试

修改index.html

  • 对应用的index.html文件进行了修改,作为版本更新的一部分。
  • 触发Jenkins任务执行更新流程。
  • 控制台输出显示了从克隆代码、打包、传输文件、构建和推送Docker镜像、更新Kubernetes YAML文件到最后应用更改的全过程,并且最终结果显示为SUCCESS。
Started by user root
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/bash-update-item1-tomcat-app1-deploy
...
Successfully built a524b7dbb530
Successfully tagged harbor.xtec.com/item1/tomcat:8.5.43_20250117_025158
...
deployment.apps/item1-tomcat-app1-deployment configured
service/item1-tomcat-app1-service unchanged
[2025-01-17 02:52:09] Kubernetes ������������������
[2025-01-17 02:52:09] ���������������: 11 ���
Finished: SUCCESS

查看更新前后的结果

  • 截图:更新前后对比
  • 更新前后对比

测试回滚

Started by user root
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/bash-update-item1-tomcat-app1-deploy
...
deployment.apps/item1-tomcat-app1-deployment rolled back
[2025-01-17 01:32:03] ������������
[2025-01-17 01:32:03] ���������������: 0 ���
Finished: SUCCESS