Skip to content
0

Kubernetes - DevOps

DevOps

项目开发需要考虑的维度:

  • Dev:怎么开发
  • Ops:怎么运维
  • 高并发:怎么承担高并发
  • 高可用:怎么做到高可用

什么是 DevOp

image-20230706232251424

DevOps: Development 和 Operations 的组合:

  • DevOps 看作开发(软件工程)、技术运营和质量保障(QA)三者的交集
  • 突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠
  • DevOps 希望做到的是软件产品交付过程中 IT 工具链的打通,使得各个团队减少时间损耗,更加高效地协同工作。专家们总结出了下面这个 DevOps 能力图,良好的闭环可以大大增加整体的产出

image-20230706232401614

什么是 CI & CD

image-20230706232436903

持续集成(Continuous Integration)

持续集成是指软件个人研发的部分向软件整体部分交付,频繁进行集成以便更快地发现其中的错误。「持续集成」源自于极限编程(XP),是 XP 最初的 12 种实践之一。

CI 需要具备这些:

  • 全面的自动化测试。这是实践持续集成&持续部署的基础,同时,选择合适的自动化测试工具也极其重要
  • 灵活的基础设施。容器,虚拟机的存在让开发人员和 QA 人员不必再大费周折
  • 版本控制工具。如 Git,CVS,SVN 等
  • 自动化的构建和软件发布流程的工具,如 Jenkins,flow.ci
  • 反馈机制。如构建/测试的失败,可以快速地反馈到相关负责人,以尽快解决达到一个更稳定的版本

持续交付(Continuous Delivery)

持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」(production-like environments)中。持续交付优先于整个产品生命周期的软件部署,建立在高水平自动化持续集成之上。灰度发布。

持续交付和持续集成的优点非常相似:

  • 快速发布。能够应对业务需求,并更快地实现软件价值
  • 编码 -> 测试 -> 上线 -> 交付的频繁迭代周期缩短,同时获得迅速反馈
  • 高质量的软件发布标准。整个交付过程标准化、可重复、可靠
  • 整个交付过程进度可视化,方便团队人员了解项目成熟度
  • 更先进的团队协作方式。从需求分析、产品的用户体验到交互 设计、开发、测试、运维等角色密切协作,相比于传统的瀑布式软件团队,更少浪费

持续部署(Continuous Deployment)

持续部署是指当交付的代码通过评审之后,自动部署到生产环境中。持续部署是持续交付的最高阶段。这意味着,所有通过了一系列的自动化测试的改动都将自动部署到生产环境。它也可以被称为「Continuous Release」。

开发人员提交代码,持续集成服务器获取代码,执行单元测试,根据测试结果决定是否部署到预演环境,如果成功部署到预演环境,进行整体验收测试,如果测试通过,自动部署到产品环境,全程自动化高效运转。

持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。

You build it, you run it,这是 Amazon 一年可以完成 5000 万次部署,平均每个工程师每天部署超过 50 次的核心秘籍。

下图是由 Jams Bowman 绘制的持续交付工具链图

img

命名空间

开始构建 DevOps 流程。

创建一个命名空间 kube-devops 实现 devops

kube-devops-namespace.yaml

yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin
  namespace: kube-devops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: jenkins-admin
    namespace: kube-devops

构建:

sh
kubectl apply -f kube-devops-namespace.yaml

Gitlab

GitLab 是一个用于仓库管理系统的开源项目,使用 Git 作为代码管理工具,并在此基础上搭建起来的 Web 服务。

Gitlab 是被广泛使用的基于 git 的开源代码管理平台, 基于 Ruby on Rails 构建, 主要针对软件开发过程中产生的代码和文档进行管理, Gitlab 主要针对 group 和 project 两个维度进行代码和文档管理, 其中 group 是群组, project 是工程项目, 一个 group 可以管理多个 project , 可以理解为一个群组中有多项软件开发任务, 而一个 project 中可能包含多个 branch, 意为每个项目中有多个分支, 分支间相互独立, 不同分支可以进行归并。

安装 GitLab 官方推荐 至少 4G 的内存,否则可能会卡顿或者运行非常慢,所以建议采用 4G 以上的云服务进行测试,或者本地搭建虚拟机的方式来做。

安装

非 Docker 安装

sh
# 下载安装包
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm

# 安装
rpm -i gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm

# 编辑 /etc/gitlab/gitlab.rb 文件
# 修改 external_url 访问路径 http://<ip>:<port>
# 其他配置修改如下
gitlab_rails['time_zone'] = 'Asia/Shanghai'
puma['worker_processes'] = 2
sidekiq['max_concurrency'] = 8
postgresql['shared_buffers'] = "128MB"
postgresql['max_worker_processes'] = 4
prometheus_monitoring['enable'] = false

# 更新配置并重启
gitlab-ctl reconfigure
gitlab-ctl restart

Docker 安装

下载 Gitlab

sh
docker pull gitlab/gitlab-ce

配置,数据,日志进行持久化,即使用数据卷,先创建三个目录

sh
mkdir -p /docker/gitlab/etc
mkdir -p /docker/gitlab/log
mkdir -p /docker/gitlab/data

启动 Gitlab

sh
docker run -d --name gitlab \
-p 8051:443 \
-p 8050:80 \
--hostname 192.168.199.31 \
--restart=always \
-v /docker/gitlab/etc:/etc/gitlab \
-v /docker/gitlab/log:/var/log/gitlab \
-v /docker/gitlab/data:/var/opt/gitlab \
-v /etc/localtime:/etc/localtime:ro \
gitlab/gitlab-ce:latest

然后前往 docker/gitlab/etc,编辑 gitlab.rb 文件。

修改如下内容:

sh
external_url 'http://192.168.199.31' # 因为是容器内部,所以不需要填写端口,默认 80
gitlab_rails['time_zone'] = 'Asia/Shanghai'
puma['worker_processes'] = 2 # 或者 1
sidekiq['max_concurrency'] = 8 # 或者 2
postgresql['shared_buffers'] = "128MB" # 或者 64MB
postgresql['max_worker_processes'] = 4 # 或者 2
prometheus_monitoring['enable'] = false

浏览器访问:http://192.168.199.31:8050/

页面配置

访问 Gitlab 后,我们可以进行一些页面的初始化配置:

sh
# 如果是 Docker 安装,则进入 Gitlab 容器
docker exec -it gitlab bash

# 查看默认密码
cat /etc/gitlab/initial_root_password
# 登录后修改默认密码 > 右上角头像 > Perferences > Password

# 修改系统配置:点击左上角三横 > Admin
# Settings > General > Account and limit > 取消 Gravatar enabled > Save changes

# 关闭用户注册功能
# Settings > General > Sign-up restrictions > 取消 Sign-up enabled > Save changes

# 开启 webhook 外部访问
# Settings > Network > Outbound requests > Allow requests to the local network from web hooks and services 勾选

# 设置语言为中文(全局)
# Settings > Preferences > Localization > Default language > 选择简体中文 > Save changes

# 设置当前用户语言为中文
# 右上角用户头像 > Preferences > Localization > Language > 选择简体中文 > Save changes

配置 Secret

创建 Gitlab 的一个默认用户名密码 secret 给 Jenkins,方便 Jenkins 后续实现自动化部署,快速访问 Gitlab。

因为后面 Jenkins 在 k8s 部署,所以利用 k8s 的 secret 来管理用户名和密码。

sh
cd /opt/gitlab

echo root > ./username
echo youngkbt > password
kubectl create secret generic git-username-password --from-file=./username --from-file=./password -n kube-devops

为项目配置 Webhook

进入项目点击侧边栏设置 > Webhooks 进入配置即可。

URL:在 Jenkins 创建 pipeline 项目后(下面会按照 Jenkins)

触发来源:

  • 推送事件:表示收到新的推送代码就会触发
  • 标签推送事件:新标签推送才会触发
  • 评论:根据评论决定触发
  • 合并请求事件:创建、更新或合并请求触发

添加成功后,可以在下方点击测试按钮查看 jenkins 是否成功触发构建操作。

卸载

sh
# 停止服务
gitlab-ctl stop

# 卸载 rpm 软件(注意安装的软件版本是 ce 还是 ee)
rpm -e gitlab-ce

# 查看进程
ps -ef|grep gitlab
# 干掉第一个 runsvdir -P /opt/gitlab/service log 进程

# 删除 gitlab 残余文件
find / -name *gitlab* | xargs rm -rf
find / -name gitlab | xargs rm -rf

Harbor

安装

sh
# 下载 harbor 安装包
wget https://github.com/goharbor/harbor/releases/download/v2.8.2/harbor-offline-installer-v2.8.2.tgz

# 解压后执行 install.sh 就行
tar -zxvf harbor-offline-installer-v2.8.2.tgz

cd harbor

复制配置模版

sh
cp harbor.yml.tmpl harbor.tmpl

进入配置文件 harbor.tmpl,修改 hostname 为本机地址,如 192.168.199.31。注释掉 https(如果不申请证书,只用 http 必须注释掉。否则会自动找 https 证书从而报错)。可以修改端口 port,默认是 80,我修改为 8040。其他保持默认。

安装 harbor

sh
./install.sh

安装完成后检查服务是否起来

sh
docker-compose ps

浏览器访问:http://192.168.199.31:8040,默认账号密码:admin / Harbor12345 ,密码在 harbor.yml 文件中可修改。

Harbar 自启

创建配置文件

sh
vim /etc/systemd/system/harbor.service

添加如下内容

sh
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor

[Service]
Type=simple
Restart=on-failure
RestartSec=5
ExecStart=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml down
# ExecStart=/usr/local/bin/docker-compose -f {{ harbor_install_path }}/harbor/docker-compose.yml up
# ExecStop=/usr/local/bin/docker-compose -f {{ harbor_install_path }}/harbor/docker-compose.yml down

[Install]
WantedBy=multi-user.target

其中 换成自己的 harbor 安装路径。 还有 docker-compose 的绝对路径,请通过 which docker-compose 查看。

然后启动该项服务:

sh
sudo systemctl enable harbor
sudo systemctl start harbor

配置 Secret

创建 Harbor 的一个默认用户名密码 secret 给 Jenkins,方便 Jenkins 后续实现自动化部署,快速访问 Harbor。

因为 k8s 去 Harbor 拉去镜像需要登录,所以利用 k8s 的 secret 来管理用户名和密码。

sh
# 创建 harbor 访问账号密码(需要将下访问的配置信息改成你自己的)
kubectl create secret docker-registry harbor-secret --docker-server=192.168.199.31:8040 --docker-username=admin --docker-password=YoungKbt1234 -n kube-devops

如果某个服务器需要连接 Harbar,则需要添加 Harbar 的地址。

sh
vim /etc/docker/daemon.json

在文件中添加如下内容:

sh
{
  "insecure-registries": ["192.168.199.31:8040"]
}

重新加载 daemon:

sh
systemctl daemon-reload
systemctl restart docker

SonarQube

这里 SonarQube 安装 k8s 里面。

安装

创建 sonarqube.yaml

yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sonarqube-data
  namespace: kube-devops
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "managed-nfs-storage" # 前面有常见过这个制备器
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sonarqube
  namespace: kube-devops
  labels:
    app: sonarqube
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sonarqube
  template:
    metadata:
      labels:
        app: sonarqube
    spec:
      initContainers:
        - name: init-sysctl
          image: busybox:1.28.4
          command: ["sysctl", "-w", "vm.max_map_count=262144"]
          securityContext:
            privileged: true
      containers:
        - name: sonarqube
          image: sonarqube
          ports:
            - containerPort: 9000
          env:
            - name: SONARQUBE_JDBC_USERNAME # 数据库用户名
              value: "sonarUser"
            - name: SONARQUBE_JDBC_PASSWORD #  数据库密码
              value: "123456"
            - name: SONARQUBE_JDBC_URL
              value: "jdbc:postgresql://postgres-sonar:5432/sonarDB"
          livenessProbe:
            httpGet:
              path: /sessions/new
              port: 9000
            initialDelaySeconds: 60
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /sessions/new
              port: 9000
            initialDelaySeconds: 60
            periodSeconds: 30
            failureThreshold: 6
          volumeMounts:
            - mountPath: /opt/sonarqube/conf
              name: data
            - mountPath: /opt/sonarqube/data
              name: data
            - mountPath: /opt/sonarqube/extensions
              name: data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: sonarqube-data
---
apiVersion: v1
kind: Service
metadata:
  name: sonarqube
  namespace: kube-devops
  labels:
    app: sonarqube
spec:
  type: NodePort
  ports:
    - name: sonarqube
      port: 9000
      targetPort: 9000
      protocol: TCP
  selector:
    app: sonarqube

pgsql.yaml

yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: kube-devops
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "managed-nfs-storage"
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-sonar
  namespace: kube-devops
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres-sonar
  template:
    metadata:
      labels:
        app: postgres-sonar
    spec:
      containers:
        - name: postgres-sonar
          image: postgres:14.2
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: "sonarDB"
            - name: POSTGRES_USER
              value: "sonarUser"
            - name: POSTGRES_PASSWORD
              value: "123456"
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: postgres-data
---
apiVersion: v1
kind: Service
metadata:
  name: postgres-sonar
  namespace: kube-devops
  labels:
    app: postgres-sonar
spec:
  type: NodePort
  ports:
    - name: postgres-sonar
      port: 5432
      targetPort: 5432
      protocol: TCP
  selector:
    app: postgres-sonar

部署 sonarqube

sh
# 进入 /opt/k8s/devops
kubectl apply -f sonarqube/

生成服务 token

sh
# 登录到 sonarqube 后台,点击头像 > MyAccount > Security > Generate Tokens > generate 生成 token 并复制

创建 Webhook 服务

sh
# 点击顶部菜单栏的配置 > 配置(小三角) > 网络调用
Name:wolfcode-jenkins
URL:http://<sonar ip>:<sonar port>/sonarqube-webhook/

创建项目

sh
# SonarQube 顶部菜单栏 Projects > Create new project > 配置基础信息并保存 > Provide a token > Generate 生成 token > Continue

# 分别选择 Java / Maven 后,按照脚本配置 Jenkinsfile 中的 sonar 配置信息
mvn sonar:sonar -Dsonar.projectKey=k8s-cicd-demo

Jenkins

构建带 maven 环境的 jenkins 镜像

准备 apache-maven-3.9.0-bin.tar.gz、sonar-scanner-4.8.0.2856-linux.tar.gz 两个压缩包,然后创建 DockerFile

dockerfile
FROM jenkins/jenkins:2.392-jdk11
ADD ./apache-maven-3.9.0-bin.tar.gz /usr/local/
ADD ./sonar-scanner-4.8.0.2856-linux.tar.gz /usr/local/

USER root

WORKDIR /usr/local/
RUN mv sonar-scanner-4.8.0.2856-linux sonar-scanner-cli
RUN ln -s /usr/local/sonar-scanner-cli/bin/sonar-scanner /usr/bin/sonar-scanner

ENV MAVEN_HOME=/usr/local/apache-maven-3.9.0
ENV PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH

RUN echo "jenkins ALL=NOPASSWD: ALL" >> /etc/sudoers
USER jenkins

构建镜像到搭建的 Harbar

sh
# 构建带 maven 环境的 jenkins 镜像
docker build -t 192.168.199.31:8040/library/jenkins-maven:v1 .

# 登录 harbor
docker login -uadmin 192.168.199.31:8040

# 推送镜像到 harbor
docker push 192.168.199.31:8040/library/jenkins-maven:v1

安装 Jenkins

创建 manifests 目录,在该目录下创建几个 yaml 文件:

jenkins-configmap.yaml

yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mvn-settings
  namespace: kube-devops
  labels:
    app: jenkins-server
data:
  settings.xml: |-
    <?xml version="1.0" encoding="UTF-8"?>
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
        <localRepository>/var/jenkins_home/repository</localRepository>
        
        <mirrors>
            <mirror>
                <id>aliyun-central</id>
                <name>aliyun maven</name>
                <mirrorOf>*</mirrorOf>
                <url>https://maven.aliyun.com/repository/central/</url>
            </mirror>
            <mirror>
                <id>aliyun-public</id>
                <name>aliyun maven</name>
                <mirrorOf>*</mirrorOf>
                <url>https://maven.aliyun.com/repository/public/</url>
            </mirror>
            <mirror>
                <id>aliyun-apache-snapshots</id>
                <name>aliyun maven</name>
                <mirrorOf>*</mirrorOf>
                <url>https://maven.aliyun.com/repository/apache-snapshots</url>
            </mirror>
            <mirror>
                <id>aliyun-google</id>
                <name>aliyun maven</name><mirrorOf>*</mirrorOf>
                <url>https://maven.aliyun.com/repository/google</url>
            </mirror>
        </mirrors>

        <pluginGroups>
                <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
        </pluginGroups>
        <profiles>
            <profile>
                <id>sonar</id>
                <activation>
                        <activeByDefault>true</activeByDefault>
                </activation>
                <properties>
                    <sonar.host.url>http://sonarqube:9000</sonar.host.url>
                </properties>
            </profile>
        </profiles>
    </settings>

jenkins-deployment.yaml

yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: kube-devops
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins-server
  template:
    metadata:
      labels:
        app: jenkins-server
    spec:
      serviceAccountName: jenkins-admin
      imagePullSecrets:
        - name: harbor-secret # harbor 访问 secret
      containers:
        - name: jenkins
          image: 192.168.199.31:8040/library/jenkins-maven:v1
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
            runAsUser: 0 # 使用 root 用户运行容器
          resources:
            limits:
              memory: "2Gi"
              cpu: "1000m"
            requests:
              memory: "500Mi"
              cpu: "500m"
          ports:
            - name: httpport
              containerPort: 8080
            - name: jnlpport
              containerPort: 50000
          livenessProbe:
            httpGet:
              path: "/login"
              port: 8080
            initialDelaySeconds: 90
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 5
          readinessProbe:
            httpGet:
              path: "/login"
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          volumeMounts:
            - name: jenkins-data
              mountPath: /var/jenkins_home
            - name: docker
              mountPath: /run/docker.sock
            - name: docker-home
              mountPath: /usr/bin/docker
            - name: mvn-setting
              mountPath: /usr/local/apache-maven-3.9.0/conf/settings.xml
              subPath: settings.xml
            - name: daemon
              mountPath: /etc/docker/daemon.json
              subPath: daemon.json
            - name: kubectl
              mountPath: /usr/bin/kubectl
      volumes:
        - name: kubectl
          hostPath:
            path: /usr/bin/kubectl
        - name: jenkins-data
          persistentVolumeClaim:
            claimName: jenkins-pvc
        - name: docker
          hostPath:
            path: /run/docker.sock # 将主机的 docker 映射到容器中
        - name: docker-home
          hostPath:
            path: /usr/bin/docker
        - name: mvn-setting
          configMap:
            name: mvn-settings
            items:
              - key: settings.xml
                path: settings.xml
        - name: daemon
          hostPath:
            path: /etc/docker/

jenkins-pvc.yaml

yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: kube-devops
spec:
  storageClassName: managed-nfs-storage
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

jenkins-service.yaml

yml
apiVersion: v1
kind: Service
metadata:
  name: jenkins-service
  namespace: kube-devops
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/path: /
    prometheus.io/port: "8080"
spec:
  selector:
    app: jenkins-server
  type: NodePort
  ports:
    - port: 8080
      targetPort: 8080

jenkins-serviceAccount.yaml

yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin
  namespace: kube-devops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: jenkins-admin
    namespace: kube-devops

进入 jenkins 目录,安装 jenkins

sh
# 进入 jenkins 目录,安装 jenkins
kubectl apply -f manifests/

# 查看是否运行成功
kubectl get po -n kube-devops

# 查看 service 端口,通过浏览器访问
kubectl get svc -n kube-devops

# 查看容器日志,获取默认密码
kubectl logs -f pod名称 -n kube-devops

安装插件

如果有些插件搜不到,可以去官网下载 https://plugins.jenkins.io/

Build Authorization Token Root

构建授权 token

Gitlab

Gitlab 配置插件

SonarQube Scanner

代码质量审查工具

在 Dashboard > 系统管理 > Configure System 下面配置 SonarQube servers

Name:sonarqube # 注意这个名字要在 Jenkinsfile 中用到 Server URL:http://sonarqube:9000

Server authentication token:创建 credentials 配置为从 sonarqube 中得到的 token

进入系统管理 > 全局工具配置 > SonarQube Scanner > Add SonarQube Scanner

Name:sonarqube-scanner

自动安装:取消勾选

SONAR_RUNNER_HOME:/usr/local/sonar-scanner-cli

Node and Label parameter

节点标签参数配置

Kubernetes

jenkins + k8s 环境配置

进入 Dashboard > 系统管理 > 节点管理 > Configure Clouds 页面

配置 k8s 集群,名称:kubernetes,点击 Kubernetes Cloud details 继续配置

Kubernetes 地址:如果 jenkins 是运行在 k8s 容器中,直接配置服务名即可 https://kubernetes.default

如果 jenkins 部署在外部,那么则不仅要配置外部访问 ip 以及 apiserver 的端口(6443),还需要配置服务证书

Jenkins 地址:

  • 如果部署在 k8s 集群内部:http://jenkins-service:8080
  • 如果在外部:http://192.168.192.168:32479(换成你们自己的)

配置完成后保存即可

Config File Provider

用于加载外部配置文件,如 Maven 的 settings.xml 或者 k8s 的 kubeconfig 等。

Git Parameter

git 参数插件,在进行项目参数化构建时使用。

创建 Gitlab 访问凭证

系统管理 > 安全 > Manage Credentials > System > 全局凭据(unrestricted) > Add Credentials

范围:全局

用户名:root

密码:youngkbt

ID:gitlab-user-pass

案例:SpringBoot 项目 CICD

点击创建任务 > Pipeline(流水线)。

参数化构建:可以通过配置参数化构建,将经常变动的参数提取出来,方便每次构建时传入。

构建触发器:勾选基于 gitlab webhook 的构建方式,同时拿到 webhook 地址,且在高级中生成 secret token,将其配置到 gitlab 项目中。

根据需求选择 webhook 的触发事件。

流水线:选择定义为 Pipeline script from SCM 从远程仓库拉取 Jenkinsfile 配置。

配置 SCM 为 Git。

Repositories:

  • Repository URL:仓库地址
  • Credentials:仓库访问的账号密码
  • Branches to build:选择拉取哪个分支下的代码
  • 脚本路径:Jenkinsfile 脚本文件名称以及所在路径

PS:当项目下载下来后,所有操作都默认在项目根目录下执行。

配置节点标签

系统管理 > 节点管理 > 列表中 master 节点最右侧的齿轮按钮

修改标签的值与项目中 Jenkinsfile 中 agent > kubernetes > label 的值相匹配

创建流水线项目

在首页点击 Create a Job 创建一个流水线风格的项目

Webhook 构建触发器

Jenkins 流水线项目 Webhook 配置

在 Jenkins 项目配置下找到构建触发器栏目

勾选 Build when a change is pushed to GitLab. GitLab webhook URL: http://192.168.113.121:31216/project/k8s-cicd-demo 上方的 URL 就是用于配置到 gitlab 项目 webhook 的地址。

启用 Gitlab 构建触发器:

  • Push Events:勾选,表示有任意推送到 Git 仓库的操作都会触发构建
  • Opend Merge Request Events:勾选,表示有请求合并时触发构建

点击高级 > Secret Token > Generate 按钮,生成 token

保存以上配置

GitLab 项目 Webhook 配置

进入 GitLab 项目设置界面 > Webhooks

将上方 Jenkins 中的 URL 配置到 URL 处

将上方生成的 Secret Token 配置到 Secret 令牌

按照需求勾选触发来源,这里我依然勾选 推送事件、合并请求事件

取消 SSL 验证

点击添加 webhook 按钮,添加后可以点击测试确认链接是否可以访问

Pipeline 脚本配置

流水线:选择定义为 Pipeline script from SCM 从远程仓库拉取 Jenkinsfile 配置。

配置 SCM 为 Git。

Repositories:

  • Repository URL:仓库地址
  • Credentials:仓库访问的账号密码
  • Branches to build:选择拉取哪个分支下的代码
  • 脚本路径:Jenkinsfile 脚本文件名称以及所在路径

检查/创建相关凭证

Harbor 镜像仓库凭证

通过系统管理 > Manage Credentials > 凭据 > System > 全局凭证 > Add Credentials 添加 Username with password 类型凭证

填写好用户名密码后,需要注意凭证 id 要与 Jenkinsfile 中的 *DOCKER_CREDENTIAL_ID* 一致

Gitlab 访问凭证

通过系统管理 > Manage Credentials > 凭据 > System > 全局凭证 > Add Credentials 添加 Username with password 类型凭证

填写好用户名密码后,需要注意凭证 id 要与 Jenkinsfile 中的 GIT_CREDENTIAL_ID 一致

kubeconfig 文件 id

  1. 事先安装 Config File Provider 插件
  2. 进入系统管理 > Mapped files > Add a new Config 添加配置文件
  3. Type 选择 Custom file 点击 next
  4. 在 k8s master 节点执行 cat ~/.kube/config 查看文件内容,并将所有内容复制
  5. 将复制的内容贴到 Config file 的 Content 中后点击 Submit 保存并提交
  6. 复制保存后文件 id 到 Jenkinsfile 中的 KUBECONFIG_CREDENTIAL_ID

SonarQube 凭证

  1. 进入 SonarQube 系统,点击右上角用户头像 > 我的账号 进入设置页面
  2. 点击 安全 > 填写令牌名称 > 点击生成按钮生成 token > 复制生成后的 token
  3. 进入 jenkins 添加凭证管理页面,添加 Secret Text 类型的凭证,将 token 贴入其中
  4. 保证凭证 id 与 Jenkinsfile 文件中的 SONAR_CREDENTIAL_ID 一致

添加 SonarQube Webhook

  1. 进入 SonarQube 管理页面,点击顶部菜单栏的配置 > 配置(小三角) > 网络调用

  2. 点击右侧创建按钮创建新的 Webhook,并填写名称与地址

    名称:jenkins 地址:http://jenkins访问ip:端口/sonarqube-webhook/

项目构建

方式一:在 Jenkins 管理后台,进入项目中点击立即构建进行项目构建。

方式二:在开发工具中修改代码,并将代码提交到远程仓库自动触发构建。

Jenkinsfile

一般我们都会在项目根目录创建一个 Jenkinsfile 文件和 Dockerfile 文件,让 Jenkins 按照 Jenkinsfile 的规则去触发 CICD,让 Docker 按照 Dockerfile 文件去构建镜像。

这里给一个模板:

groovy
pipeline {
    agent {
        kubernetes {
            label "maven" // 指定有 maven 标签的节点,Dashboard -> 系统管理 -> 节点列表,匹配有 maven 标签的节点
        }
    }

    environment {
        REGISTRY = "192.168.199.31:8040" // Harbor 仓库
        DOCKER_CREDENTIAL_ID = "harbor-username-password" // Jenkins 全局凭据 ID:Harbor 登录的用户名密码
        DOCKERHUB_NAMESPACE = "library" // Harbar 仓库
        GIT_REPO_URL = "192.168.199.31:8050" // Gitlab 仓库地址
        GIT_CREDENTIAL_ID = "gitlab-username-password" // Jenkins 全局凭据 ID:Gitlab 登录的用户名密码
        GIT_ACCOUNT = "root" // Gitlab 用户名
        APP_NAME = "test-jenkins" // 部署的项目名
        SONAR_SERVER_URL = "192.168.199.28:30112" // sonarqube 质量检测地址
        SONAR_CREDENTIAL_ID = "sonarqube-token" // Jenkins 全局凭据 ID:sonarqube 的认证 token
        KUBE_CONFIG_CREDENTIAL_ID = "f1fd50fc-f462-42b8-8aa5-8055fdca05dd"
    }
    // 可选的参数构建
    parameters {
        gitParameter name: "BRANCH_NAME", branch: "", branchFilter: "origin/(.*)", defaultValue: "master", description: "请选择要发布的分支", quickFilterEnabled: false, selectedValue: "DEFAULT", sortMode: "NONE", tagFilter: "*", type: "PT_BRANCH"
        choice(name: "NAMESPACE", choices: ["kube-devops"], description: "命名空间")
        string(name: "TAG_NAME", defaultValue: "snapshot", description: "标签名称,必须以 v 开头,例如:v1、v1.0.0")
    }

    stages {

        stage("checkout branch") {
            steps {
                checkout scmGit(branches: [[name: "$BRANCH_NAME"]], extensions: [], userRemoteConfigs: [[credentialsId: "$GIT_CREDENTIAL_ID", url: "http://$GIT_REPO_URL/$GIT_ACCOUNT/test-jenkins.git"]])
            }
        }

        stage("unit test") {
            steps {
                sh "mvn clean test" // Maven 单元测试
            }
        }
        // 代码质量检查
        stage("sonarqube analysis") {
            agent none
            steps {
                // sonarqube 认证,连接服务端进行代码质量检查
                withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: "SONAR_TOKEN")]) {
                    withSonarQubeEnv("sonarqube") {
                        sh '''
                                mvn sonar:sonar -Dsonar.projectKey=$APP_NAME
                                echo "mvn sonar:sonar -Dsonar.projectKey=$APP_NAME"
                           '''
                    }
                    // 代码质量检测超时为 5 分组
                    timeout(time: 5, unit: "MINUTES", activity: true) {
                        waitForQualityGate "true"
                    }

                }

            }
        }
        // 构建进行,推到 Harbor,版本跟随 Jenkins 的构建次数变化
        stage("build & push") {
            steps {
                sh "mvn clean package -DskipTests" // 跳过单元测试,上面已经单元测试了
                // 构建镜像,tag 为 Jenkins 内置的 BUILD_NUMBER,即 Jenkins 的构建次数
                sh "docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER ."
                // 认证 Harbor,推到 Harbor
                withCredentials([usernamePassword(credentialsId: "$DOCKER_CREDENTIAL_ID", usernameVariable: "DOCKER_USERNAME", passwordVariable: "DOCKER_PASSWORD")]) {
                    sh '''
                            echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin
                            docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER
                       '''
                }
            }
        }
        // 推到 Harbor,版本是 latest
        stage("push latest") {
            when {
                branch "master" // master 分支才往下触发该 stage
            }
            steps {
                sh "docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest"
                sh "docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest"
            }
        }
        // k8s 测试环境部署
        stage("deploy to dev") {
            steps {
                input(id: "deploy-to-dev", message: "deploy to dev?") // Jenkins 构建到这会阻塞,等待用户选择是否才继续往下执行
                // 替换 deploy 下的 yaml 文件,将预先占位的字符串改成动态参数
                sh '''
                        sed -i'' "s#REGISTRY#$REGISTRY#" deploy/test-jenkins-dev.yaml
                        sed -i'' "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/test-jenkins-dev.yaml
                        sed -i'' "s#APP_NAME#$APP_NAME#" deploy/test-jenkins-dev.yaml
                        sed -i'' "s#BUILD_NUMBER#$BUILD_NUMBER#" deploy/test-jenkins-dev.yaml
                        kubectl apply -f deploy/test-jenkins-dev.yaml
                   '''
            }
        }
        // 在 Gitlab 发布 tag
        stage("push with tag") {
            agent none
            when {
                expression {
                    params.TAG_NAME =~ /v.*/ // 只匹配版本为 v. 开头的 jar 版本,对应上面「可选的参数构建」
                }
            }
            steps {
                input(message: "release image with tag?", submitter: "")
                withCredentials([usernamePassword(credentialsId: "$GIT_CREDENTIAL_ID", usernameVariable: "GIT_USERNAME", passwordVariable: "GIT_PASSWORD")]) {
                    sh "git config --global user.email 'kele-Bingtang@qq.com'"
                    sh "git config --global user.name 'kele-Bingtang'"
                    sh "git tag -a $TAG_NAME -m '$TAG_NAME'"
                    sh "git push http://$GIT_USERNAME:$GIT_PASSWORD@$GIT_REPO_URL/$GIT_ACCOUNT/test-jenkins.git --tags --ipv4"
                }
                sh "docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME"
                sh "docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME"

            }
        }
        // k8s 生产环境部署
        stage("deploy to production") {
            agent none
            when {
                expression {
                    params.TAG_NAME =~ /v.*/
                }
            }
            steps {
                input(message: "deploy to production?", submitter: "")
                sh '''
                        sed -i\"\" "s#REGISTRY#$REGISTRY#" deploy/test-jenkins.yaml
                        sed -i\"\" "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/test-jenkins.yaml
                        sed -i\"\" "s#APP_NAME#$APP_NAME#" deploy/test-jenkins.yaml
                        sed -i\"\" "s#TAG_NAME#$TAG_NAME#" deploy/test-jenkins.yaml
                        kubectl apply -f deploy/test-jenkins.yaml
                    '''
            }
        }
    }
}

Dockerfile 文件

sh
## 基础镜像
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
FROM eclipse-temurin:8-jre

## 作者
LABEL org.opencontainers.image.authors="Kele-BingTang"

## 创建并进入工作目录
RUN mkdir -p /java
WORKDIR /java

## maven 插件构建时得到 buildArgs 中的值
COPY target/*.jar app.jar

## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms256m -Xmx256m"

## 暴露端口
EXPOSE 8080

## 容器启动命令
## CMD 第一个参数之后的命令可以在运行时被替换
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
最近更新