在K8S实现CICD

在K8S实现CICD


前言

我们要做什么?

开发人员在本地merge完毕代码,push到代码仓库之后,如果是develop开发分支的代码则发布到k8s测试环境,如果是master分支的代码则发布到k8s生产环境

整体规划

注意:

1、但凡部署一个k8s集群,对cpu的要求是至少有2個核,实验环境自己玩的话,建议2G,如果自己的实验机不够,可以考虑1.5G

2、k8s工具集群需要运行jenkins、gitlab、harbor,需要消耗大量内存,建議8核16G,

3、工具集群与实际运行应用的测试或生产集群隔离开始有好处的,防止不必要的资源争抢

4、本套架构实际在公司里是用了3套k8s集群,我们想在实验环境里复现这套公司的完整架构,考虑到资源问题,k8s集群均采用单节点部署,多节点部署见
k8s部署应该是一个基本功。

5、有两套方案

(1)gitlab(配置Auto DevOPS)-》K8s

(2)gitlab-》jenkins->k8s(本文采用该方案)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZVY4GLJ-1689070560529)(/media/202306/2023-06-26_145957_4284170.8485550136061444.png)]

一 安装k8s

(1)基本配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QVWWFpeM-1689070560529)(/media/202306/2023-06-26_150018_3274490.12122526753387619.png)]

1、配置静态ip地址

2、 关闭NetworkManager

systemctl stop NetworkManager
systemctl disable NetworkManager

3、关闭selinux与防火墙

sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
systemctl stop firewalld.service
systemctl disable firewalld.service

4、关闭swap分区

# Kubernetes 1.8开始要求关闭系统的Swap,如果不关闭,默认配置下kubelet将无法启动,所以我们有两种处理方式,采用一种即可
方式一:关闭swap分区
swapoff -a  # 先临时关闭,立即生效
sed -i 's/.*swap.*/#&/' /etc/fstab # 注释掉swap,永久关闭,保证即便重启主机也会生效
 
方式二: kubelet忽略swap
echo 'KUBELET_EXTRA_ARGS="--fail-swap-on=false"' > /etc/sysconfig/kubelet

5、配置主机名

hostnamectl set-hostname master

6、更新系统软件(排除内核)

yum install epel-release -y && yum update -y --exclud=kernel*

7、安装基础常用软件

yum install wget expect vim net-tools ntp bash-completion ipvsadm ipset jq iptables conntrack sysstat libseccomp -y

# 其他(选做)
yum -y install python-setuptools python-pip gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel \
zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel zip unzip ncurses ncurses-devel curl curl-devel e2fsprogs \
e2fsprogs-devel krb5-devel libidn libidn-devel openssl openssh openssl-devel nss_ldap openldap openldap-devel openldap-clients \
openldap-servers libxslt-devel libevent-devel ntp libtool-ltdl bison libtool vim-enhanced python wget lsof iptraf strace lrzsz \
kernel-devel kernel-headers pam-devel tcl tk cmake ncurses-devel bison setuptool popt-devel net-snmp screen perl-devel \
pcre-devel net-snmp screen tcpdump rsync sysstat man iptables sudo libconfig git  bind-utils \
tmux elinks numactl iftop bwm-ng net-tools expect

8、更新系统内核(docker 对系统内核要求比较高,最好使用4.4+),非必须操作,推荐做(注意:3.x内核与5.x内核加载的ipvs模块名是不同的,后续加载时需要注意一下)

一般来说,只有从https://www.kernel.org/ 下载并编译安装的内核才是官方内核,可以看出目前的稳定版版本为5.18.10

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3GhwdJtb-1689070560530)(/media/202306/2023-06-26_150114_2316170.43159386305829217.png)]
不过,大多数 Linux 发行版提供自行维护的内核,可以通过 yum 或 rpm 等包管理系统升级。

ELRepo是一个为Linux提供驱动程序和内核映像的存储库,这里的升级方案就是采用ELRepo提供的内核通道。

ELRepo官网:http://elrepo.org/tiki/tiki-index.php

# 1、升级系统内核
 
#查看 yum 中可升级的内核版本
yum list kernel --showduplicates
#如果list中有需要的版本可以直接执行 update 升级,多数是没有的,所以要按以下步骤操作
 
#导入ELRepo软件仓库的公共秘钥
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
 
#Centos7系统安装ELRepo
yum -y install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
#Centos8系统安装ELRepo
#yum -y install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
 
#查看ELRepo提供的内核版本
yum --disablerepo="*" --enablerepo="elrepo-kernel" list available
 
#(1) kernel-lt:表示longterm,即长期支持的内核,会不断修复一些错误;当前为5.4.208。建议安装lt版
yum --enablerepo=elrepo-kernel install kernel-lt.x86_64 -y
 
#(2) kernel-ml:表示mainline,即当前主线的内核;会加入一些新功能。
# 若想安装主线内核则执行: yum --enablerepo=elrepo-kernel install kernel-ml.x86_64 -y
 
#查看系统可用内核,并设置启动项
sudo awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
 
#0 : CentOS Linux (5.17.1-1.el7.elrepo.x86_64) 7 (Core)
#1 : CentOS Linux (3.10.0-1160.53.1.el7.x86_64) 7 (Core)
#2 : CentOS Linux (3.10.0-1160.el7.x86_64) 7 (Core)
#3 : CentOS Linux (0-rescue-20220208145000711038896885545492) 7 (Core)
 
#指定开机启动内核版本
grub2-set-default 0 # 或者 grub2-set-default 'CentOS Linux (5.17.1-1.el7.elrepo.x86_64) 7 (Core)'
 
#生成 grub 配置文件
grub2-mkconfig -o /boot/grub2/grub.cfg
 
#查看当前默认启动的内核
grubby --default-kernel
 
#重启系统,验证
uname -r

(2)安装k8s

1、安装docker

# 1、选做,卸载之前的docker
yum -y remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine

# 2、安装docker所需安装包
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install docker-ce -y
 
# 3、启动并设置开机启动
systemctl start docker && systemctl enable docker && systemctl status docker
 
# 4、基本配置
cat > /etc/docker/daemon.json << EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors":["https://reg-mirror.qiniu.com/"],
"live-restore":true
}
EOF
# 5、重启
systemctl restart docker && docker info

2、拉取镜像到本地

kubeadm部署时会去指定的地址拉取镜像,该地址在墙外无法访问,所以我们从阿里云拉取,并tag为指定的地址即可

#1、=====>编写脚本
cat > dockpullImages1.18.1.sh << EOF
#!/bin/bash
##所需要的镜像名字
#k8s.gcr.io/kube-apiserver:v1.18.1
#k8s.gcr.io/kube-controller-manager:v1.18.1
#k8s.gcr.io/kube-scheduler:v1.18.1
#k8s.gcr.io/kube-proxy:v1.18.1
#k8s.gcr.io/pause:3.2
#k8s.gcr.io/etcd:3.4.3-0
#k8s.gcr.io/coredns:1.6.7
###拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.18.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.18.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.18.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.18.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.4.3-0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.6.7
###修改tag
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.18.1 k8s.gcr.io/kube-apiserver:v1.18.1
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.18.1 k8s.gcr.io/kube-controller-manager:v1.18.1
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.18.1 k8s.gcr.io/kube-scheduler:v1.18.1
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.18.1 k8s.gcr.io/kube-proxy:v1.18.1
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2 k8s.gcr.io/pause:3.2
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.4.3-0 k8s.gcr.io/etcd:3.4.3-0
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.6.7 k8s.gcr.io/coredns:1.6.7
 
EOF
 
# 2、执行该脚本
sh dockpullImages1.18.1.sh

3、安装kubelet、kubeadm 和 kubectl

kubelet 运行在 Cluster 所有节点上,负责启动 Pod 和容器。
kubeadm 用于初始化 Cluster。
kubectl 是 Kubernetes 命令行工具。通过 kubectl 可以部署和管理应用,查看各种资源,创建、删除和更新各种组件。

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
 
sed -ri 's/gpgcheck=1/gpgcheck=0/g' /etc/yum.repos.d/kubernetes.repo 

安装

1.安装
yum makecache fast
# 注意,如果直接执行 yum install -y kubelet kubeadm kubectl ipvsadm 默认是下载最新版本v1.22.2
======================================================================
[root@master ~]# yum install -y kubelet-1.18.1-0.x86_64 kubeadm-1.18.1-0.x86_64 kubectl-1.18.1-0.x86_64 ipvsadm 
 
2.加载ipvs相关内核模块
yum install -y conntrack-tools ipvsadm ipvsadmin ipset conntrack libseccomp 
 
如果重新开机,需要重新加载(可以写在 /etc/rc.local 中开机自动加载)
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
#modprobe nf_conntrack_ipv4 # 如果是3.x内核,那么应该加载这一行
modprobe nf_conntrack # 如果是高版本内核比如5.x,那么应该加载这个。在高版本内核已经把nf_conntrack_ipv4替换为nf_conntrack了。
 
3.编辑文件添加开机启动
cat >> /etc/rc.local << EOF
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack
#modprobe nf_conntrack_ipv4 # 如果是3.x内核,那么应该加载这一行、注释掉上面那一行
 
EOF
 
chmod +x /etc/rc.local
 
4.配置:
配置转发相关参数,否则可能会出错
cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF
 
5.使配置生效
sysctl --system
 
6.如果net.bridge.bridge-nf-call-iptables报错,加载br_netfilter模块
# modprobe br_netfilter
# sysctl -p /etc/sysctl.d/k8s.conf
 
7.查看是否加载成功
[root@master ~]# lsmod | grep ip_vs
ip_vs_sh               16384  0 
ip_vs_wrr              16384  0 
ip_vs_rr               16384  0 
ip_vs                 159744  6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack          151552  5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs
nf_defrag_ipv6         24576  2 nf_conntrack,ip_vs
libcrc32c              16384  4 nf_conntrack,nf_nat,xfs,ip_vs

4、启动kubelet

#1.配置kubelet使用pause镜像
#配置变量:
systemctl start docker && systemctl enable docker
DOCKER_CGROUPS=$(docker info | grep 'Cgroup Driver' | cut -d' ' -f4)
echo $DOCKER_CGROUPS
 
#这个是使用国内的源。-###注意我们使用谷歌的镜像--操作下面的第3标题
#2.配置kubelet的cgroups
cat >/etc/sysconfig/kubelet<<EOF
KUBELET_EXTRA_ARGS="--cgroup-driver=$DOCKER_CGROUPS --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2"
EOF
 
# 上述操作本质就是写了类似下面的内容
#cat >/etc/sysconfig/kubelet<<EOF
#KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs --pod-infra-container-image=k8s.gcr.io/pause:3.2"
#EOF

启动

systemctl daemon-reload
systemctl enable kubelet && systemctl restart kubelet
 
# 注意在这里使用 # systemctl status kubelet,你会发现报错误信息;
# 7月 10 23:28:36 master systemd[1]: Unit kubelet.service entered failed state.
# 7月 10 23:28:36 master systemd[1]: kubelet.service failed.
 
#运行 # journalctl -xefu kubelet 命令查看systemd日志会发现提示缺少一些问题件
#这个错误在运行kubeadm init 生成CA证书后会被自动解决,此处可先忽略。
#简单地说就是在kubeadm init 之前kubelet会不断重启。

5、初始化master,注意修改apiserver-advertise-address为master节点ip

kubeadm init \
--kubernetes-version=v1.18.1 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-advertise-address=172.16.10.14 \
--ignore-preflight-errors=Swap

参数解释:

–kubernetes-version: 用于指定k8s版本;
–apiserver-advertise-address:用于指定kube-apiserver监听的ip地址,就是 master本机IP地址。
–pod-network-cidr:用于指定Pod的网络范围; 10.244.0.0/16
–service-cidr:用于指定SVC的网络范围;
–image-repository: 指定阿里云镜像仓库地址

看到以下信息表示安装成功

Your Kubernetes control-plane has initialized successfully!
 
To start using your cluster, you need to run the following as a regular user:
 
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
 
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/
 
Then you can join any number of worker nodes by running the following on each as root:
 
kubeadm join 172.16.10.14:6443 --token n3mvgw.56ul27rjtox7fr3n \
    --discovery-token-ca-cert-hash sha256:b93e84284d278e4056ee5a1b0370a20f49a1878df8a3492f5f855e2d5141e6e7 

成功后注意最后一个命令,这个join命令可以用来添加节点,不添加的话默认当前master节点同时会被当做一个node节点加到k8s集群中。

注意保持好kubeadm join,后面会用到的。

如果初始化失败,请使用如下代码清除后重新初始化

 kubeadm reset

6、按照提示配置kubectl

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
 
# 查看
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

7、kubernetes出于安全考虑默认情况下无法在master节点上部署pod,于是用下面方法去掉master节点的污点:

# 1、kubeadm init创建完集群后,当你部署pod时,查看kubectl describe pod会发现问题
3 node(s) had taints that the pod didn't tolerate.
 
# 2、解决方法
kubectl taint nodes --all node-role.kubernetes.io/master-

8、配置使用网络插件

要让 Kubernetes Cluster 能够工作,必须安装 Pod 网络,否则 Pod 之间无法通信。

Kubernetes 支持多种网络方案,这里我们先使用 flannel,后面还会讨论 Canal。

flannel.yaml

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: psp.flannel.unprivileged
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
    seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
    apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
    apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
  privileged: false
  volumes:
  - configMap
  - secret
  - emptyDir
  - hostPath
  allowedHostPaths:
  - pathPrefix: "/etc/cni/net.d"
  - pathPrefix: "/etc/kube-flannel"
  - pathPrefix: "/run/flannel"
  readOnlyRootFilesystem: false
  # Users and groups
  runAsUser:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  # Privilege Escalation
  allowPrivilegeEscalation: false
  defaultAllowPrivilegeEscalation: false
  # Capabilities
  allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
  defaultAddCapabilities: []
  requiredDropCapabilities: []
  # Host namespaces
  hostPID: false
  hostIPC: false
  hostNetwork: true
  hostPorts:
  - min: 0
    max: 65535
  # SELinux
  seLinux:
    # SELinux is unused in CaaSP
    rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: flannel
rules:
- apiGroups: ['extensions']
  resources: ['podsecuritypolicies']
  verbs: ['use']
  resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "flannel",
          "delegate": {
            "hairpinMode": true,
            "isDefaultGateway": true
          }
        },
        {
          "type": "portmap",
          "capabilities": {
            "portMappings": true
          }
        }
      ]
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  selector:
    matchLabels:
      app: flannel
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
      hostNetwork: true
      priorityClassName: system-node-critical
      tolerations:
      - operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: registry.cn-hangzhou.aliyuncs.com/alvinos/flanned:v0.13.1-rc1
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conflist
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: registry.cn-hangzhou.aliyuncs.com/alvinos/flanned:v0.13.1-rc1
        command:
        - /opt/bin/flanneld
        args:
        - --ip-masq
        - --kube-subnet-mgr
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
          limits:
            cpu: "100m"
            memory: "50Mi"
        securityContext:
          privileged: false
          capabilities:
            add: ["NET_ADMIN", "NET_RAW"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run/flannel
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
      - name: run
        hostPath:
          path: /run/flannel
      - name: cni
        hostPath:
          path: /etc/cni/net.d
      - name: flannel-cfg
        configMap:
          name: kube-flannel-cfg

部署flannel

kubectl apply -f flannel.yaml

查看

[root@master ~]# kubectl get nodes
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   6m49s   v1.18.1
 
[root@master ~]# kubectl  get pods -n kube-system
NAME                             READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-mtrkv         1/1     Running   0          3m57s
coredns-66bff467f8-qhwv4         1/1     Running   0          3m57s
etcd-master                      1/1     Running   0          4m9s
kube-apiserver-master            1/1     Running   0          4m9s
kube-controller-manager-master   1/1     Running   0          4m9s
kube-flannel-ds-b25g6            1/1     Running   0          32s
kube-proxy-gkqjv                 1/1     Running   0          3m57s
kube-scheduler-master            1/1     Running   0          4m9s

二 在工具集群安装jenkins

持续的构建与发布在日常工作中必不可少,而基于k8s的CI/CD有很多工具可用,比如jenkins、Gitlab CI以及新兴的drone之类的,目前大多数公司仍然是部署jenkins集群来定制符合自己要求的CI/CD流程,所以本文采用的是jenkins

在工具集群安装jenkins,所以下述操作均在test06上执行

2.1 创建jenkins.yaml文件

采用的镜像jenkins/jenkins:lts是jenkins官网的默认镜像,若相定制自己的jenkins镜像,比如将一些插件直接打包到镜像里,可以参考:https://github.com/jenkinsci/docker

下面有两个版本的jenkins.yaml,推荐使用版本二,两者唯一的不同就是,版本二将jenkins的插件源mirrors.jenkins-ci.org解析到了127.0.0.1:80(安装完后可以进入pod内查看/etc/hosts文件,会多一条解析),然基于sidecar设计模式启动了一个监听127.0.0.1:80的nginx,该nginx将收到的请求都转发到了清华源,以此提升插件下载速度

版本一:

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
  labels:
    type: local
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - "ReadWriteOnce"
  hostPath: 
    path: /data/jenkins
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
spec:
  accessModes:
  - "ReadWriteOnce"
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts-jdk11
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 1500m
            memory: "6Gi"
          requests:
            cpu: 1500m
            memory: "2048Mi"
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        volumeMounts:
        - name: jenkins-home
          mountPath: /var/jenkins_home
      volumes:
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-init-service
spec:
  type: NodePort
  ports:
  - port: 7096
    name: web1
    nodePort: 7096
    targetPort: 8080
  - port: 50000
    name: web2
    nodePort: 50000
    targetPort: 50000
  selector:
    app: jenkins

版本二:

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
  labels:
    type: local
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - "ReadWriteOnce"
  hostPath: 
    path: /data/jenkins
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
spec:
  accessModes:
  - "ReadWriteOnce"
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jenkins-mirror-conf
data:
  nginx.conf: |
    user nginx;
    worker_processes  3;
    error_log  /dev/stderr;
    events {
      worker_connections  10240;
    }
    http {
      log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" $request_time';
      access_log    /dev/stdout main;
      server {
          listen 80;
          server_name mirrors.jenkins-ci.org;
          location / {
            proxy_redirect off;
            proxy_pass https://mirrors.tuna.tsinghua.edu.cn/jenkins/;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Accept-Encoding "";
            proxy_set_header Accept-Language "zh-CN";
          }
          index index.html index.htm index.php;
          location ~ /\. {
            deny all;
          }
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      hostAliases:
      - ip: "127.0.0.1"
        hostnames:
        - "mirrors.jenkins-ci.org"
      containers:
      - name: mirror
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx 
          readOnly: true
          name: nginx-conf
      - name: jenkins
        image: jenkins/jenkins:lts-jdk11
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 1500m
            memory: "6Gi"
          requests:
            cpu: 1500m
            memory: 2048Mi
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        volumeMounts:
        - name: jenkins-home
          mountPath: /var/jenkins_home
      volumes:
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins-pvc
      - name: nginx-conf
        configMap:
          name: jenkins-mirror-conf
          items:
          - key: nginx.conf
            path: nginx.conf
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-init-service
spec:
  type: NodePort
  ports:
  - port: 7096
    name: web1
    nodePort: 7096
    targetPort: 8080
  - port: 50000
    name: web2
    nodePort: 50000
    targetPort: 50000
  selector:
    app: jenkins

2.2 创建本地目录

本例中pv用的是本地路径,所以需要在本机创建目录,该目录会作为jenkins master的pv挂载到/var/jenkins_home下

mkdir -p /data/jenkins
chmod o+w /data/jenkins

2.3 部署jenkins-master

如果直接部署会报错

The Service "jenkins-init-service" is invalid: spec.ports[0].nodePort: Invalid value: 7096: provided port is not in the valid range. The range of valid ports is 30000-32767

意思为k8s运行的svc端口范围只能在30000-32767,解决方法为

kubernetes默认端口号范围是 30000-32767 ,如果期望值不是这个区间则需要更改。
1、找到配置文件里,一般的在这个文件夹下: /etc/kubernetes/manifests/
2、找到文件名为kube-apiserver.yaml 的文件,也可能是json格式
3、编辑添加配置 service-node-port-range=1024-65535,如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H8IkZzvX-1689070560530)(/media/202306/2023-06-26_150606_3472190.5160812013443139.png)]
需要等待一会,k8s会自动加载配置,加载完毕后执行安装

kubectl apply -f jenkins.yaml

ps:会自动生成root的密码,用于登录jenkins的web界面,可以查看密码

# 如果你部署的是版本1那么查看
kubectl  exec -ti jenkins-548bfffcb-mtc4t  cat /var/jenkins_home/secrets/initialAdminPassword
# 如果你部署的是版本2那么查看
kubectl  exec -ti jenkins-7c7c67bf9c-hvqms -c jenkins cat /var/jenkins_home/secrets/initialAdminPassword

2.4 关于jenkins-slave储备知识

jenkins主从模式介绍:
英文简称为 Master-Slave,基于分而治之、解耦合的核心思想,将一庞大的工作拆分,master主要负责基本管理、提供操作入口,例如流水线的创建与修改等,至于流水线的具体执行则交给slave去做。

为何要有主从模式呢?
因为日常构建 Jenkins 任务中,会经常出现下面的情况:
1、自动化测试需要消耗大量的 CPU 和内存资源,如果服务器上还有其他的服务,可能会造成卡顿或者宕机;
2、Jenkins 平台项目众多,如果同一时间构建大量的任务,会出现多个任务抢占资源的情况。
Jenkins 提供了主从模式(Master-Slave) 解决这个问题。我们可以为 Jenkins 配置多台 slave 从机,当 slave 从机和 Jenkins 服务建立连接之后,由 Jenkins 发指令给指定的 slave 从机运行任务,消耗的资源由 slave 从机去承担。

传统的jenkins一主多从架构是有缺点的

1、主 Master 发生单点故障时,整个流程都不可用了
2、每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲,
3、资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。或者有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态,
如下图,我们将master跑在k8s里提供故障恢复能力,并且配置动态创建pod slave来解决上述2、3问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CSftCOXv-1689070560531)(/media/202306/2023-06-26_150639_1711310.4818021934277654.png)]
那什么是动态pod slave呢?
需要知道:传统的主从,是静态的,会造成镜像冗余,单个镜像很大
例如针对不同的程序比如java、go

你需要定义一个java的slave镜像,用于构建java环境,为了完成流水线,该镜像里需要有以下工具

1、拉代码:安装git工具

2、测试:代码检测工具

3、编译:java环境

4、打镜像:docker工具

5、推送到k8s:kubectl工具

你还需要定义一个go的slave镜像,用于构建go环境,为了完成流水线,该镜像里需要有以下工具

1、拉代码:安装git工具

2、测试:代码检测工具

3、编译:go环境

4、打镜像:docker工具

5、推送到k8s:kubectl工具

你会发现go的这个slave的镜像与java这个slave镜像需要安装的冗余部分太多

会造成镜像很大没必要

2.5小节里安装k8s插件,就是为了动态创建pod,每次构建都会动态产生一个pod在里面运行slave,该slave

pod里会启动多个容器用于流水线的不同阶段

此时针对工具,我们可以

1、制作一个安装git的小镜像

2、制作一个安装有代码检测工具的小镜像

3、制作一个镜像里面安装docker与kubectl工具

针对编译环境

1、为了构建java程序我们做一个安装有java环境的小镜像

2、为了构建go程序则做一个有go环境的小镜像

然后在动态的pod Template里组合使用上面的容器,构建java的流水线与构建go的流水线可以共用上面做的1、2、3小镜像

具体怎么实现呢?请看2.5小节

2.5 安装并配置k8s插件(用于动态创建slave pod)

接下来我们就需要来配置 Jenkins,让他能够动态的生成 Slave 的 Pod。

步骤1

我们需要安装 kubernetes 插件, 点击 Manage Jenkins -> Manage Plugins -> Available -> Kubernetes 勾选安装即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wzn9ZlLH-1689070560531)(/media/202306/2023-06-26_150743_1979780.5473396912438083.png)]

步骤2

先制作一个用于jenkins链接k8s工具集群的凭据(注意是工具集群,jenkins的pod slave肯定是要运行在工具集群里的嘛)

此功能是用于在k8s中启动一个jenkins节点pod,在cloud中已添加的集群名称下添加一个pod模板,例如有maven或者是其他打包工具的镜像,然后在新建项目中勾选限制项目的运行节点,选择云中配置的某个模板,执行此任务时就会在对应的k8s集群中启动一个用于运行该任务的pod,运行完成后自动销毁。
 
注意:这玩意不是让你用来使用kubectl命令执行上线部署的
 
首先需要生成jenkins所需的k8s密钥
 
在kubectl命令服务器上找到当初配置连接集群时的 config 文件,位置在 ~/.kube/config
 
# 命令
certificate_authority_data=$(awk -F': ' '/certificate-authority-data/{print $2}' ~/.kube/config)
client_certificate_data=$(awk -F': ' '/client-certificate-data/{print $2}' ~/.kube/config)
client_key_data=$(awk -F': ' '/client-key-data/{print $2}' ~/.kube/config)
 
echo "$certificate_authority_data" | base64 -d > ca.crt
echo "$client_certificate_data" | base64 -d > client.crt
echo "$client_key_data" | base64 -d > client.key
 
# 再生成jenkins使用的PKCS12格式的cert.pfx文件,需要设置密码,注意密码后期jenkins需要
# openssl pkcs12 -export -out cert.pfx -inkey client.key -in client.crt -certfile ca.crt
Enter Export Password: 在此输入密码egon123
Verifying - Enter Export Password: 在此输入密码egon123

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FANkPnqd-1689070560531)(/media/202306/2023-06-26_150804_8828180.11062114778501575.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSaM5lPI-1689070560531)(/media/202306/2023-06-26_150810_2752720.11046539275512102.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zMSTwWC-1689070560532)(/media/202306/2023-06-26_150816_6860670.5829576289468812.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cgNIePrZ-1689070560532)(/media/202306/2023-06-26_150821_8210270.4279185615441333.png)]

步骤3

安装好了kubernetes 插件,又有了凭据,接下来我可以配置jenkins链接k8s集群,需要注意的是配置方式新版与旧版不同

目前使用版本为Jenkins (2.346.1)为新版本

旧版找到配置按钮:

点击 Manage Jenkins —> Configure System —> (拖到最下方),如果有 Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTdunEbE-1689070560532)(/media/202306/2023-06-26_150830_0900270.06379379171203048.png)]
新版本的 Kubernetes 插件将配置单独放置到了一个页面中链接地址为:http://172.16.10.16:7096/configureClouds/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxiIhJyi-1689070560532)(/media/202306/2023-06-26_150838_6261570.357716979880205.png)]
链接测试通过后会显示:Connected to Kubernetes v1.18.1

接着上一步继续配置(如果上一步没有点击保存,则直接继续配置即可)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXmG0EVK-1689070560533)(/media/202306/2023-06-26_150846_8311110.20530096363141626.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R07klAX7-1689070560533)(/media/202306/2023-06-26_150853_8004310.5054887369834744.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QkCvxlN-1689070560533)(/media/202306/2023-06-26_150858_5835750.670910813468798.png)]
查看jenkins的svc地址,填入,然后保存,下图为何可以填入jenkins的svc地址,是因为工具相关都部署在k8s集群中,例如后面部署的gitlab就可以用svc地址访问jenkins,因为都是在一个k8s集群里嘛!

[root@test06 ~]# kubectl  -n default get svc |grep jenkins
jenkins-init-service   NodePort    10.100.229.121   <none>        7096:7096/TCP,50000:50000/TCP   88m

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQzNEVgN-1689070560533)(/media/202306/2023-06-26_150914_6684630.37682620103061937.png)]

步骤4:手动创建一个Pod Template

配置 Pod Template,本质就是配置 Jenkins Slave 运行的 Pod 模板,为了能够让大家快速看到jenkins slave以一个pod的方式动态启动与销毁的效果,此处我们就先手动创建一个Pod Template,你需要事先知道的时候,这个Pod Template在后面我们是可以通过流水线代码自定义的,不同的流水线可以定义自己单独的Pod Template,完全不需要你在jenkins的web页面里手动创建一个固定死了的Pod Template,我们此处手动创建只是会提前让你体验一下动态床pod slave的效果而已,后期这个手动创建的固定死了的pod template都是可以删掉的。

手动创建一个Pod Template如下图操作,这里尤其注意Labels/标签列表 ,它非常重要,后面执行Job会通过该值选中,然后我们这里使用的是 cnych/jenkins:jnlp6 这个镜像,这个镜像是在官方的 jnlp 镜像基础上定制的,加入了 docker、kubectl 等一些实用的工具,

再次强调:此处我们添加一个pod Template只是为了用于案例演示(它的配置包括镜像啥的对我们来说都没啥用),后期我们会删掉该pod Template,然后用pipeline脚本定制pod Template、自己选择镜像,所以本小节手动创建一个Pod Template这一小节的所有步骤都只是为了演示,对后续的实际应用均没啥用。

系统配置->节点管理->Configure Clouds->Pod Templates->添加Pod模板->Pod Templates details

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2inrGw0-1689070560533)(/media/202306/2023-06-26_150949_4274910.7918261464228786.png)]
然后我们这里需要在下面挂载两个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件已经打包到上面的镜像中了,另外一个目录下 /root/.kube 目录,我们将这个目录挂载到容器的 /root/.kube 目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jxVP7by-1689070560534)(/media/202306/2023-06-26_151000_7752850.5135524642252596.png)]

了解docker in docker

1、docker in docker 是什么?
docker in docker即在docker容器内运行docker指令
通常不建议在docker容器内运行docker,但在有些场景和业务上,比如我们的CICD,如果agent运行在容器里,我们就是需要在该容器内使用docker命令,比如执行docker pull 拉取镜像,以及docker build构建镜像等操作,docker命令都是提交给了docker的守护进程,所以agent里是需要能够访问到docker守护进程的,如何访问呢?通过套接字文件即可,具体做法就是把宿主机中运行的docker服务的套接字文件映射到jenkins agent容器中即可
 
2、如何实现docker in docker
把宿主机中运行的docker服务的套接字文件docker.sock挂载jenkins agent容器中,实现共享宿主机的docker.socket,这就使得在容器中可以使用宿主机上的docker daemon。
如此,我们便可以在容器内部使用docker pull\push\build image\run等命令了(这些命令的执行都是在与宿主机上面的docker daemon通信)
 
3、示例
docker run -it --name docker-daemon --hostname daemon-test --network=host -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -e DOCKER_HOST="unix:///var/run/docker.sock" centos:7 /bin/bash
–network: 指定容器的网络, 启动容器默认使用bridge网络,这里直接使用主机的网络
-e:设置环境变量,这里直接指定使用docker.sock访问docker daemon
-v: 挂载文件,直接将主机的docker.sock挂载至容器内,共享docker daemon;挂载docker命令脚本至容器内,共享docker服务

另外,我们在步骤5,启动 Slave Pod 执行测试命令时,有一条kubectl get pods查看pod信息,需要k8s权限才行,所以我们需要创建一个ServiceAccount

jenkinsAcount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins
rules:
  - apiGroups: ["extensions", "apps"]
    resources: ["deployments", "ingresses"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create","delete","get","list","patch","update","watch"]
  - apiGroups: [""]
    resources: ["pods/log", "events"]
    verbs: ["get","list","watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: jenkins
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: default

然后在 Slave Pod 配置的地方点击下面的高级,添加上对应的 ServiceAccount 即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhA0b6Eo-1689070560534)(/media/202306/2023-06-26_151105_5020470.4464676589683707.png)]

步骤5:测试pod Template

创建一个job任务,测试能否启动一个pod来运行slave,并观察job运行完毕后pod是否自动销毁

jenkins首页->新建任务->输入一个任务名称、选择Freestyle project 类型的任务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Guuwvx6C-1689070560534)(/media/202306/2023-06-26_151120_7424970.5800700835375897.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q1sWNvmo-1689070560534)(/media/202306/2023-06-26_151146_1267550.04236676573396536.png)]
然后往下拉,在 Build 区域选择Execute shell

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6DWzkZv-1689070560534)(/media/202306/2023-06-26_151152_9223120.0426708215454078.png)]
填入测试命令,点击保存

echo "测试 Kubernetes 动态生成 jenkins slave"
echo "==============docker in docker==========="
docker info
 
echo "=============kubectl============="
kubectl get pods
 
sleep 120

然后点击构建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6yw32MIq-1689070560535)(/media/202306/2023-06-26_151205_7766850.1346267654795531.png)]
查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUolBrbo-1689070560535)(/media/202306/2023-06-26_151212_9275340.7096742963069186.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ri7kmkXQ-1689070560535)(/media/202306/2023-06-26_151217_7278710.8059480678642418.png)]

三 部署gitlab

注意,可以把gitlab也安装到工具集群中,但是会gitlab占用资源较多,需要注意一下

3.1 在工具集群部署gitlab

Gitlab 主要涉及到3个应用:Redis、Postgresql、Gitlab 核心程序,

我们这里选择使用的镜像不是官方的,而是 Gitlab 容器化中使用非常多的一个第三方镜像:sameersbn/gitlab,基本上和官方保持同步更新,地址:http://www.damagehead.com/docker-gitlab/

如果我们已经有可使用的 Redis 或 Postgresql 服务的话,那么直接配置在 Gitlab 环境变量中即可,如果没有的话就单独部署,我们这里为了展示 gitlab 部署的完整性,还是分开部署。

首先部署需要的 Redis 服务,对应的资源清单文件如下:(gitlab-redis.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: default
  labels:
    name: redis
spec:
  selector:
    matchLabels:
      name: redis
  template:
    metadata:
      name: redis
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        resources:
          limits:
            cpu: 1
            memory: "2Gi"
          requests:
            cpu: 1
            memory: "2048Mi"
        image: sameersbn/redis:4.0.9-2
        imagePullPolicy: IfNotPresent
        ports:
        - name: redis
          containerPort: 6379
        volumeMounts:
        - mountPath: /var/lib/redis
          name: data
        livenessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 30
          timeoutSeconds: 1
      volumes:
      - name: data
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
  labels:
    name: redis
spec:
  ports:
    - name: redis
      port: 6379
      targetPort: redis
  selector:
    name: redis

然后是数据库 Postgresql,对应的资源清单文件如下:(gitlab-postgresql.yaml),存储pv、pvc根据自己的需求设置,

本例采用hostPath,先在主机创建目录

mkdir -p /data/postgresql

gitlab-postgresql.yaml文件内容如下,存储空间推进20Gi,但是因为是测试环境设置为10G

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgresql-pv
  namespace: default
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - "ReadWriteOnce"
  hostPath: 
    path: /data/postgresql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgresql-pvc
  namespace: default
spec:
  accessModes:
  - "ReadWriteOnce"
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql
  namespace: default
  labels:
    name: postgresql
spec:
  selector:
    matchLabels:
      name: postgresql
  template:
    metadata:
      name: postgresql
      labels:
        name: postgresql
    spec:
      containers:
      - name: postgresql
        resources:
          limits:
            cpu: 4
            memory: "4Gi"
          requests:
            cpu: 2
            memory: "2048Mi"
        image: sameersbn/postgresql:10-2
        imagePullPolicy: IfNotPresent
        env:
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: passw0rd
        - name: DB_NAME
          value: gitlab_production
        - name: DB_EXTENSION
          value: pg_trgm
        - name: USERMAP_UID
          value: "999"
        - name: USERMAP_GID
          value: "999"
        ports:
        - name: postgres
          containerPort: 5432
        volumeMounts:
        - mountPath: /var/lib/postgresql
          name: data
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 30
          timeoutSeconds: 1
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgresql-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: default
  labels:
    name: postgresql
spec:
  ports:
    - name: postgres
      port: 5432
      targetPort: postgres
  selector:
    name: postgresql

然后就是我们最核心的 Gitlab 的应用,对应的资源清单文件如下:(gitlab.yaml),存储也使用hostPath,请依据自己的情况填写存储,但是考虑到性能,还是建议用本地存储,可以考虑用localpath的存储类

创建目录

mkdir -p /data/gitlab

yaml文件内容如下

apiVersion: v1
kind: PersistentVolume
metadata:
  name: gitlab-pv
  namespace: default
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - "ReadWriteOnce"
  hostPath: 
    path: /data/gitlab
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-pvc
  namespace: default
spec:
  accessModes:
  - "ReadWriteOnce"
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab
  namespace: default
  labels:
    name: gitlab
spec:
  selector:
    matchLabels:
      name: gitlab
  template:
    metadata:
      name: gitlab
      labels:
        name: gitlab
    spec:
      initContainers:
      - name: fix-permissions
        image: busybox
        command: ["sh", "-c", "chown -R 1000:1000 /home/git/data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /home/git/data
      containers:
      - name: gitlab
        resources:
          requests:
            cpu: 2
            memory: "2Gi"
          limits:
            cpu: 4
            memory: "4Gi"
        image: sameersbn/gitlab:12.9.5
        imagePullPolicy: IfNotPresent
        env:
        - name: TZ
          value: Asia/Shanghai
        - name: GITLAB_TIMEZONE
          value: Beijing
        - name: GITLAB_SECRETS_DB_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_SECRETS_SECRET_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_SECRETS_OTP_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_ROOT_PASSWORD
          value: 'egon@666'
        - name: GITLAB_ROOT_EMAIL
          value: 18611453110@163.com
        - name: GITLAB_HOST
          value: git.k8s.local            # 该域名会是你后面从gitlab里拉取项目的地址,需要添加解析才行
        - name: GITLAB_PORT               # 这个端口很重要,与svc对应好
          value: "1180"
        - name: GITLAB_SSH_PORT           # 这个端口很重要,与svc对应好
          value: "30022"
        - name: GITLAB_NOTIFY_ON_BROKEN_BUILDS
          value: "true"
        - name: GITLAB_NOTIFY_PUSHER
          value: "false"
        - name: GITLAB_BACKUP_SCHEDULE
          value: daily
        - name: GITLAB_BACKUP_TIME
          value: 01:00
        - name: DB_TYPE
          value: postgres
        - name: DB_HOST
          value: postgresql
        - name: DB_PORT
          value: "5432"
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: passw0rd
        - name: DB_NAME
          value: gitlab_production
        - name: REDIS_HOST
          value: redis
        - name: REDIS_PORT
          value: "6379"
        ports:
        - name: http
          containerPort: 80
        - name: ssh
          containerPort: 22
        volumeMounts:
        - mountPath: /home/git/data
          name: data
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 60
          timeoutSeconds: 1
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: gitlab-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: gitlab
  namespace: default
  labels:
    name: gitlab
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: http
      nodePort: 1180
    - name: ssh
      port: 22
      targetPort: ssh
      nodePort: 30022
  selector:
    name: gitlab

查看

[root@test06 ~]# kubectl  get pods |grep -v jenkins
NAME                         READY   STATUS    RESTARTS   AGE
gitlab-5954f566bf-lcl7j      1/1     Running   0          99s
postgresql-8b5cfff54-5qfdl   1/1     Running   0          108s
redis-bb67747bf-bnkzd        1/1     Running   0          103s
 
[root@test06 ~]# kubectl  get svc |grep gitlab
gitlab                 NodePort    10.97.217.189    <none>        80:1180/TCP,22:30022/TCP        110s

强调:gitlab相关信息

关于gitlab.yaml中的一些环境变量的设置
        - name: GITLAB_ROOT_PASSWORD
          value: 'egon@666'
        - name: GITLAB_ROOT_EMAIL
          value: 18611453110@163.com
        - name: GITLAB_HOST
          value: git.k8s.local
        - name: GITLAB_PORT
          value: "1180"
        - name: GITLAB_SSH_PORT
          value: "30022"
 
# 1、账号
账号名:root
密码为:egon@666
 
# 2、svc采用nodePort
web访问端口固定为80
SSH端口固定为30022
 
# 3、结合GITLAB_HOST:git.k8s.local
gitlab的web访问地址为:
    http://git.k8s.local:1180
 
gitlab的ssh访问地址为
    git.k8s.local:30022
 
# 4、GITLAB_HOST:git.k8s.local用来访问gitlab的主机,我们设置为域名,需要为访问者添加指向该域名的地址解析
因为我们的svc采用的是nodeport,并且端口固定,所以搭配一个k8s节点的ip地址就可以访问到
所以我们选取gitlab所在k8s集群中的任意一个节点的ip地址,我们就一个节点,地址为172.16.10.16
所以我们让git.k8s.local解析到该地址就可以
 
需要访问gitlab的有哪些,就在哪里添加解析
    1、一个是k8s的工具集群,里面安装有jenkins需要访问gitlab
    2、另外一个是开发机
    3、需要访问gitlab的web界面的用户

3.2 添加解析

在工具集群添加自定义解析

# 1、添加自定义解析
[root@test06 ~]# kubectl  -n kube-system edit cm coredns
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        hosts { # 添加自定义解析
          172.16.10.16 git.k8s.local
          fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2022-07-12T02:37:57Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data: {}
    manager: kubeadm
    operation: Update
    time: "2022-07-12T02:37:57Z"
 
# 2、重启coredns
kubectl  -n kube-system scale deployment coredns --replicas=0
kubectl  -n kube-system scale deployment coredns --replicas=2
 
# 3、测试
[root@test06 ~]# cat test.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test
  name: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  strategy: {}
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - image: centos:7
        imagePullPolicy: IfNotPresent
        name: test
        command: ["sh","-c","tail -f /dev/null"]
[root@test06 ~]# kubectl apply -f test.yaml
[root@test06 ~]# kubectl  exec -ti test-75bf4b886f-xkht7 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
sh-4.2# 
sh-4.2# ping git.k8s.local
PING git.k8s.local (172.16.10.16) 56(84) bytes of data.
64 bytes from git.k8s.local (172.16.10.16): icmp_seq=1 ttl=64 time=0.024 ms
64 bytes from git.k8s.local (172.16.10.16): icmp_seq=2 ttl=64 time=0.024 ms

在开发机172.16.10.17添加解析

echo "172.16.10.16 git.k8s.local" >> /etc/hosts

在要访问gitlab的web界面进行操作的主机添加hosts文件解析,或者干脆用ip地址172.16.10.16访问也行

比如我们要在windows主机以域名的方式访问gitlab的webui,那么配置HOSTS解析

# 编辑文件,路径如下
C:\Windows\System32\drivers\etc\HOSTS
 
# 
添加解析
172.16.10.16 git.k8s.local

3.3 登录创建项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zKcflSC-1689070560535)(/media/202306/2023-06-26_151415_3104840.6355752390387.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ctbqq8C0-1689070560536)(/media/202306/2023-06-26_151420_0812190.6948612040059644.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Zcn4M0B-1689070560536)(/media/202306/2023-06-26_151428_9426900.5514679799639219.png)]
点击Clone可以获取到gitlab中项目

1、SSH方式链接地址

ssh://git@git.k8s.local:30022/root/greenhat.git

2、http方式链接地址

http://git.k8s.local:1180/root/greenhat.git

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQM2eQ2Z-1689070560536)(/media/202306/2023-06-26_151440_3165750.2820118142695026.png)]

四 开发机配置登录gitlab

按照事先的规划 172.16.10.17充当开发机的角色

4.1 为开发配置访问gitlab域名的解析

该步骤之前已经做过了,知道就行

echo "172.16.10.16 git.k8s.local" >> /etc/hosts

4.2 在开发机制作秘钥对

[root@test07 ~]# ssh-keygen  # 一路回车
[root@test07 ~]# cd /root/.ssh/
[root@test07 .ssh]# ls
id_rsa  id_rsa.pub
[root@test07 .ssh]# cat id_rsa.pub  # 查看公钥
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRRndlXF4tKTxp1ukEzdXwuj58Com1alMgfQSGbSM6GiDk8Xupx5gnpIjg8S0e6HsMtvwM723lv2R0IEgD0HO+FdZ9KzjN++YU2EtloBAHr18C46e9ASViB/IqoE3r4TzW33EfmKtFwx/EHNl0tJg5RhEirz5ZcGp2SF+ApTpOv+AWfim1kZqt8KgIuBfAd7ezDM5+8j750JVQKfn/xL7TMA6TlHA7qSlXML/UDZPaWIkkWq2nshosVAcZvW8IN/JBlK3K9A9TCdl0kVD7MzMGYtDmlhLxhNQj1vy52kioMFlxNJlIJ7JyxkiTJsIKJO9kkIgmEPndlJ+3pYVSZg/B root@test07

4.3 复制粘贴公钥到gitlab里

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4u3lBFRL-1689070560536)(/media/202306/2023-06-26_151511_2642510.5586424845744464.png)]

4.4 开发机器模拟更新代码

因为我们贴到gitlab里的是ssh的公钥,所以此处我们用git的项目的ssh地址:ssh://git@git.k8s.local:30022/root/greenhat.git

yum install git -y
git clone ssh://git@git.k8s.local:30022/root/greenhat.git
 
git config --global user.name "root"
git config --global user.email "email@example.com"
git remote add origin ssh://git@git.k8s.local:30022/root/greenhat.git
 
cat > test.sh << EOF
echo ok > /tmp/a.log
sleep 100000
 
EOF   
 
git add .
git commit -m "第一次提交"
git push origin

可以到gitlab里看到更新的内容

五 部署harbor

1、创建harbor命名空间

后续将Harbor相关的服务都部署在该命名空间中。

kubectl create namespace harbor

2、创建NFS外部供应商

本处使用NFS为存储,需要提供外部供应商,如果你有该供应商,可跳过本步骤。

步骤一:部署NFS服务端,在工具集群,本例中为172.16.10.16

yum  install -y  nfs-utils
systemctl start nfs && systemctl enable nfs 
systemctl status nfs
chkconfig nfs on              #设置为开机自启
mkdir  -p  /data/nfs/harbor        #创建共享目录
 
cat > /etc/exports <<EOF
/data/nfs/harbor   172.16.10.16/24(rw,no_root_squash)
EOF
 
systemctl restart nfs
showmount  -e localhost                     #检查共享目录信息

步骤二:安装客户端

 # 本处客户端即为kubernets集群的每一个节点,若Pod调度到的节点没有该服务,则无法使用对应的存储卷。
 yum -y install nfs-utils
 systemctl start nfs-utils &&  systemctl enable nfs-utils 
 systemctl status  nfs-utils

步骤三:创建NFS provisioner,\nfs-provisioner.yaml****

cat > nfs-provisioner.yaml << EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
  namespace: harbor
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-provisioner-cr
rules:
 - apiGroups: [""]
   resources: ["persistentvolumes"]
   verbs: ["get", "list", "watch", "create", "delete"]
 - apiGroups: [""]
   resources: ["persistentvolumeclaims"]
   verbs: ["get", "list", "watch", "update"]
 - apiGroups: ["storage.k8s.io"]
   resources: ["storageclasses"]
   verbs: ["get", "list", "watch"]
 - apiGroups: [""]
   resources: ["events"]
   verbs: ["create", "update", "patch"]
 - apiGroups: [""]
   resources: ["services", "endpoints"]
   verbs: ["get"]
 - apiGroups: ["extensions"]
   resources: ["podsecuritypolicies"]
   resourceNames: ["nfs-provisioner"]
   verbs: ["use"]
 
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    namespace: harbor
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-cr
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: nfs-role
  namespace: harbor
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get","list","watch","create","update","patch"]
 
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
  namespace: harbor
subjects:
 - kind: ServiceAccount
   name: nfs-provisioner
   namespace: harbor
roleRef:
 kind: Role
 name: nfs-role
 apiGroup: rbac.authorization.k8s.io
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-proversitioner
  namespace: harbor
spec:
  selector:
    matchLabels:
      app: nfs-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner
      containers:
      - name: nfs-provisioner
        image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
          - name: PROVISIONER_NAME
            value: example.com/nfs
          - name: NFS_SERVER
            value: 172.16.10.16   # NFS服务端地址
          - name: NFS_PATH
            value: /data/nfs/harbor
      volumes:
      - name: nfs-client-root
        nfs:
          server: 172.16.10.16   #  NFS服务端地址
          path: /data/nfs/harbor  # NFS共享目录
EOF

步骤四:部署

kubectl apply -f nfs-provisioner.yaml
kubectl -n harbor get pod
 
# 显示
NAME                                  READY   STATUS    RESTARTS   AGE
nfs-proversitioner-5c6f96d484-bvb7d   1/1     Running   0          6s

步骤五:创建存储类

Harbor的database和redis组件是为有状态服务,需要对Harbor数据做持久化存储。

本处基于NFS创建StorageClass存储类,NFS服务器和共享目录为:

NFS服务器地址:172.16.10.16
NFS共享目录:/data/nfs/harbor

cat > harbor-storageclass.yaml << EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: harbor-storageclass
provisioner: example.com/nfs #指定外部存储供应商,这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
  archiveOnDelete: "false"
 
EOF

部署

kubectl apply  -f harbor-storageclass.yaml
kubectl -n harbor  get storageclass
 
# 显示
NAME                  PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
harbor-storageclass   example.com/nfs   Delete          Immediate           false                  0s

3、安装helm

官网:https://github.com/helm/helm/releases

wget https://get.helm.sh/helm-v3.9.0-linux-amd64.tar.gz
tar xf helm-v3.9.0-linux-amd64.tar.gz -C /data/
 
cat >> /etc/profile << EOF
export PATH=/data/linux-amd64:$PATH
EOF
 
source /etc/profile

4、添加仓库地址

helm repo add harbor https://helm.goharbor.io
 
helm repo  list                  # 查看添加的Chart

5、下载Chart包到本地

因为需要修改的参数比较多,在命令行直接helm install比较复杂,我就将Chart包下载到本地,再修改一些配置,这样比较直观,也比较符合实际工作中的业务环境。

helm pull harbor/harbor   # 下载Chart包
tar zxvf harbor-1.8.2.tgz   # 解压包

6、修改values.yaml

$ cd harbor    
$ ls
cert  Chart.yaml  conf  LICENSE  README.md  templates  values.yaml
$ vim  values.yaml
expose:
  type: nodePort         # 我这没有Ingress环境,使用NodePort的服务访问方式。   
  tls:
    enabled: false    # 关闭tls安全加密认证(如果开启需要配置证书)
...
externalURL: http://172.16.10.16:30002   # 使用nodePort且关闭tls认证,则此处需要修改为http协议和expose.nodePort.ports.http.nodePort指定的端口号,IP即为kubernetes的节点IP地址
 
# 持久化存储配置部分
persistence:
  enabled: true   # 开启持久化存储
  resourcePolicy: "keep"
  persistentVolumeClaim:        # 定义Harbor各个组件的PVC持久卷部分
    registry:          # registry组件(持久卷)配置部分
      existingClaim: ""
    storageClass: "harbor-storageclass"           # 前面创建的StorageClass,其它组件同样配置
      subPath: ""
      accessMode: ReadWriteMany          # 卷的访问模式,需要修改为ReadWriteMany,允许多个组件读写,否则有的组件无法读取其它组件的数据
      size: 5Gi
    chartmuseum:     # chartmuseum组件(持久卷)配置部分
      existingClaim: ""
      storageClass: "harbor-storageclass"
      subPath: ""
      accessMode: ReadWriteMany
      size: 5Gi
    jobservice:    # 异步任务组件(持久卷)配置部分
      existingClaim: ""
      storageClass: "harbor-storageclass"    #修改,同上
      subPath: ""
      accessMode: ReadWriteMany
      size: 1Gi
    database:        # PostgreSQl数据库组件(持久卷)配置部分
      existingClaim: ""
      storageClass: "harbor-storageclass"
      subPath: ""
      accessMode: ReadWriteMany
      size: 1Gi
    redis:    # Redis缓存组件(持久卷)配置部分
      existingClaim: ""
      storageClass: "harbor-storageclass"
      subPath: ""
      accessMode: ReadWriteMany
      size: 1Gi
    trivy:         # Trity漏洞扫描插件(持久卷)配置部分
      existingClaim: ""
      storageClass: "harbor-storageclass"
      subPath: ""
      accessMode: ReadWriteMany
      size: 5Gi
...
harborAdminPassword: "Harbor12345"   # admin初始密码,不需要修改
...
metrics:
  enabled: true  # 是否启用监控组件(可以使用Prometheus监控Harbor指标),非必须操作
  core:
    path: /metrics
    port: 8001
  registry:
    path: /metrics
    port: 8001
  jobservice:
    path: /metrics
    port: 8001
  exporter:
    path: /metrics
    port: 8001
###以下的trace为2.4版本的功能,不需要修改

扩展:

如果不希望安装最新的版本,可以通过以下命令修改镜像版本号来安装指定的版本。
 
sed  -i   /tag/s/v2.4.2/v2.3.5/g  values.yaml
   
执行helm install安装Harbor

 helm install  harbor  .  -n harbor        # 将安装资源部署到harbor命名空间

服务验证

[root@test06 harbor]# kubectl  -n harbor get pods -w
NAME                                    READY   STATUS    RESTARTS   AGE
harbor-chartmuseum-7fd694bb8-pmllp      1/1     Running   0          55s
harbor-core-7c6944f9cf-shclb            1/1     Running   0          55s
harbor-database-0                       1/1     Running   0          55s
harbor-jobservice-bc848f555-qbd5s       1/1     Running   0          55s
harbor-nginx-f546d84f5-lslcj            1/1     Running   0          55s
harbor-notary-server-788754ccf4-xzslr   1/1     Running   0          55s
harbor-notary-signer-88496c75b-5ctj6    1/1     Running   0          55s
harbor-portal-bdc75c86b-fgn2t           1/1     Running   0          55s
harbor-redis-0                          1/1     Running   0          55s
harbor-registry-7b745f4f85-zs7lj        2/2     Running   0          55s
harbor-trivy-0                          1/1     Running   0          55s
nfs-proversitioner-5c6f96d484-bvb7d     1/1     Running   0          27m

7、登录Harbor UI界面

[root@test06 harbor]# kubectl -n harbor get  svc 
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                       AGE
harbor                 NodePort    10.104.8.173    <none>        80:30002/TCP,4443:30004/TCP   68s
harbor-chartmuseum     ClusterIP   10.98.123.149   <none>        80/TCP                        68s
harbor-core            ClusterIP   10.109.155.6    <none>        80/TCP                        68s
harbor-database        ClusterIP   10.100.99.187   <none>        5432/TCP                      68s
harbor-jobservice      ClusterIP   10.96.107.73    <none>        80/TCP                        68s
harbor-notary-server   ClusterIP   10.98.148.174   <none>        4443/TCP                      68s
harbor-notary-signer   ClusterIP   10.106.38.85    <none>        7899/TCP                      68s
harbor-portal          ClusterIP   10.105.2.1      <none>        80/TCP                        68s
harbor-redis           ClusterIP   10.109.79.173   <none>        6379/TCP                      68s
harbor-registry        ClusterIP   10.100.58.241   <none>        5000/TCP,8080/TCP             68s
harbor-trivy           ClusterIP   10.105.61.120   <none>        8080/TCP                      68s
[root@test06 harbor]# 

使用kubernetes任一节点主机IP和30002端口即可访问UI管理界面。

http://172.16.10.16:30002

admin

Harbor12345

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZvrQSSQ-1689070560536)(/media/202306/2023-06-26_151832_9489650.5284370538788816.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-22f43sag-1689070560537)(/media/202306/2023-06-26_151838_3026410.8754095710520974.png)]
8、推送镜像
配置docker添加對harbor的信任(强调:在所有k8s集群的节点都添加,包括测试、生产、工具集群)

# 如果直接登录我们的harbor仓库会报错http: server gave HTTP response to HTTPS client,需要配置insecure-registries
[root@test06 ~]# cat /etc/docker/daemon.json 
{
"exec-opts": ["native.cgroupdriver=systemd"],
"insecure-registries":["172.16.10.16:30002"], 
"registry-mirrors":["https://reg-mirror.qiniu.com/"],
"live-restore":true
}
[root@test06 ~]# systemctl restart docker

登录(如果是push操作,或者是拉取私有仓库的镜像都需要登录,后面我们需要在测试、生产拉取私有仓库镜像,在工具集群push镜像,所以三套集群都执行登录操作)

[root@test06 ~]# docker login -u admin -p Harbor12345 http://172.16.10.16:30002/
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
 
Login Succeeded

从docker.io里拉取镜像

docker pull centos:8

推送到我们自己的harbor里

docker tag centos:8 172.16.10.16:30002/online/centos:8
docker push 172.16.10.16:30002/online/centos:8

然后以后都可以从我们自己的harbor里拉取镜像了

docker pull 172.16.10.16:30002/online/centos:8

六 打通jenkins与gitlab

6.1 储备知识

在jenkins中完成构建有很多方式,我们采用常用的Pipeline/流水线

6.1.1 Pipeline

开发人员往gitlab里提交更新的代码之后,想要发布,运维人员需要执行下述步骤

1、拉取代码
2、单元测试
3、构建(配置好软件运行环境、下载依赖包、编译等过程,得到可执行的程序)
4、制作镜像
5、编写或更新yaml,使用最新的镜像,完成上线

传统的上线方式,上述步骤需要运维人员在单台或多台主机依次执行,流程步骤繁杂、无法可视化,极容易出错,回滚麻烦。

我们引入jenkins中的Pipeline,就是为了把运维人员手动在单个或多个节点的任务(例如代码拉取、单元测试、构建、部署等)连接到一起,相当于建立了一条流水线,每次上线时,只需要点击构建,即执行这条Pipeline流水线,这些任务就会安装提前设定好的样子依次在单台或多台主机执行,我们可以在jenkins界面看到整个过程,实现可视化。

6.1.2 jenkins的Pipeline有几个核心概念

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点

  • Stage:阶段,一个 Pipeline 可以定义多个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node

    • stages有如下特点 :
    • 所有 stages 会按照顺序运行,即当一个 stage 完成后,下一个 stage 才会开始
    • 只有当所有 stages 成功完成后,该构建任务 (Pipeline) 才算成功
    • 如果任何一个 stage 失败,那么后面的 stages 不会执行,该构建任务 (Pipeline) 失败
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘make’,就相当于我们平时 shell 终端中执行 make 命令一样。

6.1.3 如何创建jenkins的Pipeline

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,用到啥查啥就行

  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法

  • Pipeline 也有两种创建方法:

    • 在 Jenkins 的 Web UI 界面中输入脚本;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FpsGobPJ-1689070560537)(/media/202306/2023-06-26_152047_6615350.2213918430167876.png)]

  • 通过创建一个 Jenkinsfile 脚本文件放入项目源码库中,然后jenkins配置SCM,点击构建后拉取源代码,jenkins会从源代码/项目根目录下载入Jenkinsfile文件来执行规定的构建(推荐该方式)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqqjHqYi-1689070560537)(/media/202306/2023-06-26_152100_2795890.35089682273965983.png)]

6.2 牛刀小试

在6.1.3小节说到创建jenkins的pipeline流水线有两种方式,虽然推荐使用方式二,但我们还是了解下方式一的使用,即在 Jenkins 的 Web UI 界面中输入脚本的方式

访问jenkins:172.16.10.16:7096

主页-》新建任务/job-》输入一个任务名称,如test1-》点击流水线-》点击确定

在最下方的 Pipeline 区域选择Pipeline Script然后输入如下脚本,然后点击保存

下面的脚本只是一个简单小模板、小demo

node {
stage('Clone') {
    echo "1.Clone Stage"
}
stage('Test') {
    echo "2.Test Stage"
}
stage('Build') {
    echo "3.Build Stage"
}
stage('Deploy') {
    echo "4. Deploy Stage"
}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRZ4kstM-1689070560537)(/media/202306/2023-06-26_152120_9773130.7929037413099966.png)]
保存后,然后点击立即构建,可以查看Console Output的输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cD29mzVn-1689070560537)(/media/202306/2023-06-26_152127_4877710.731852257936164.png)]

6.3 在指定的slave中构建任务

在6.2中我们用一个简单的pipeline小demo完成了演示,但是构建任务并没有在jenkins的slave中运行。而在实际应用中,一些slave里有特定的环境,我们的构建任务必须在该环境里运行,这就需要我们指定slave运行,如何指定呢?

需要用到我们之前添加slave pod时指定的label标签,我们之前在2.4小节的步骤4里指定过一个slave pod并将其label设置为egon-test1,这个标签就是我们选中该slave pod的唯一标识

可以参照6.2小节新建一个pipeline,填入新脚本如下

node('egon-test1') {
  stage('Clone') {
    echo "1.Clone Stage"
  }
  stage('Test') {
    echo "2.Test Stage"
  }
  stage('Build') {
    echo "3.Build Stage"
  }
  stage('Deploy') {
    echo "4. Deploy Stage"
  }
}

点击构建,然后查看console output,可以看到选中了一个agent名为 test1-w0crf,而test1-w0crf就是在k8s临时创建的用于构建本次任务的slave pod,构建完毕后会自动删除该slave pod

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FM1CTLP0-1689070560538)(/media/202306/2023-06-26_152156_4561620.6236519107052064.png)]

6.4 对接k8s

重头戏在这里。。。

前面几个小节我们了解了如何创建pipeline,如何构建等基本流程,本节我们就创建一个SCM的流水线,然后完成构建后推送到k8s环境里

我们的大致思路是。以一个go程序为例

  • 一:在gitab里创建一个项目,该项目用来管理go程序,创建完毕后记录该项目的http或ssh链接信息

  • 二:在jenkins里创建一个go的流水线

构建触发器,定义一个随机字符串作为token值,并选择Pipeline script from SCM,在SCM配置好代码仓库的链接信息

注意:不需要填写任何pipeline的代码

  • 三:在gitlab里配置webhook

在gitlab里配置好webhook执行jenkins的地址,地址里包含在jenkins里生成的token值

  • 四:编写一个名为Jenkinsfile的文件(强调首字母是一个大写的字母,看清楚了),把pipeline的代码写到里面,然后扔给开发,开发人员会将该文件放到它的项目根目录下

  • 五:go开发人员通过自己的开发机上传go项目代码(该项目根目录下包含一个名为jenkinsfile的文件)到gitlab中的仓库greenhat里,然后会按照jenkinsfile的规定触发一整套流程

所以看明白没有,如果是java程序,套路也是一样,上面的一、二、三、四、五步走一遍,再创建一套针对java的就可以了

下面以一个go程序为例,演示

6.4.1、在gitab里创建一个项目,提供该项目的链接信息(略,之前已经创建过了详见3.3小节)

项目名为:greenhat,不用回头找了,我把项目的git的链接地址贴到这里了
 
# 1、项目的SSH方式链接地址
ssh://git@git.k8s.local:30022/root/greenhat.git
 
# 2、项目的http方式链接地址
http://git.k8s.local:1180/root/greenhat.git

6.4.2、在jenkins里创建一个go的流水线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ain7GvwA-1689070560538)(/media/202306/2023-06-26_152302_3826260.6474672269122657.png)]
构建触发器——》触发远程构建,按照提示的格式填入我们的jenkins地址,其中TOKEN_NAME可以随便写一个字符,但它很重要,因为在gitlab的webhook里就需要用到该地址,此处我们写的是:egonlao6

安装下面的提示信息,我们拼出一个uri地址如下,该地址需要配置到gitlab里的webhook用来webhook访问jenkins中指定的流水线。

http://172.16.10.16:7096/job/go-pipeline-demo/build?token=egonlao6

所以你应该听懂逻辑了,每创建一个jenkins的pipeline都可以指定一个身份令牌,然后依据提示拼成一个uri地址给某个webhook用

我们接下来的大致思路如下

# 一、在jenkins里创建两条用于构建不同项目的流水线
# 1.1 
go-pipeline-demo:得到它的token假设为egonlao6,拼有一个它的uri地址,假设为http://172.16.10.16:7096/job/go-pipeline-demo/build?token=egonlao6
# 1.2 
java-pipeline-demo:得到它的token假设为egonlao8,拼一个它的uri地址,假设为http://172.16.10.16:7096/job/java-pipeline-demo/build?token=egonlao8
 
# 二:
1、在gitlab里创建一个项目A
2、在gitlab里点击项目A,为其创建一个webhook,该webhook配置上http://172.16.10.16:7096/job/go-pipeline-demo/build?token=egonlao6
3、当我们往项目A里push代码,则会触发它的webook,然后会依据webhook配置的地址触发jenkins中go-pipeline-demo这条流水线的运行

1、在gitlab里创建一个项目B
2、在gitlab里点击项目B,为其创建一个webhook,该webhook配置上http://172.16.10.16:7096/job/java-pipeline-demo/build?token=egonlao8
3、当我们往项目A里push代码,则会触发它的webook,然后会依据webhook配置的地址触jenkins中java-pipeline-demo这条流水线的运行

在gitlab里创建第一个webhook,配上上一条流水线的uri地址,这就打通了某个项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHpt3xRP-1689070560538)(/media/202306/2023-06-26_152340_2277140.1490087956633318.png)]
往下拖,拖到最后,在流水线定义里选择:Pipeline script from SCM,然后在SCM哪里选择git,配置好git信息

jenkins会去git的项目地址里拉取代码,然后在代码根目录下找到一个叫jenkinsfile的文件执行,该文件里写的就是如何构建的代码

如下图配置链接git的地址后

1、红色提示可能会显示解析失败,你需要明确一点此处的配是用于jenkins访问gitlab,需要为jenkins添加解析到git.k8s.local域名的解析,jenkins运行在k8s里,所以需要在coredns添加解析,参考3.2小节完成添加即可

2、红色提示链接拒绝,需要为jenkins添加链接gitlab的认证信息,需要强调的一点是,Repository URL我们没有填入的gitlab地址是ssh协议的,是因为我们是在一个Slave Pod里完成构建,如果采用SSH的方式去访问gitlab代码仓库的话就需要频繁的去更新 SSH-KEY,所以我们这里填入http协议的链接地址,并且采用直接使用用户名和密码的形式来方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWMCwGur-1689070560538)(/media/202306/2023-06-26_152351_2461060.8802709793847103.png)]
添加Credentials

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TqzMApfd-1689070560538)(/media/202306/2023-06-26_152401_0712050.27908461139916807.png)]
往下拖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JLIe1FVd-1689070560539)(/media/202306/2023-06-26_152406_5456540.995651538418265.png)]
然后在Credentials里选择刚刚创建的凭证,此时Jenkins 就可以正常访问到 GitLab 了

接着往下拖,配置用于构建的分支,分两种情况讨论

  • 1、如果所有的分支我们都想要进行构建的话,只需要将 Branch Specifier 区域留空即可
  • 2、一般情况下不同的环境对应的分支才需要构建,比如 master、develop、test 等,平时开发的 feature 或者 bugfix 的分支没必要频繁构建,我们这里就只配置 master 和 develop 两个分支用于构建。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b15dUKb1-1689070560539)(/media/202306/2023-06-26_152421_1325910.016553839049230357.png)]
点击保存

6.4.3、在gitlab里配置webhook

前往 Gitlab 中

项目已经创建好了,我们直接为项目创建webhooks,指向上面的流水线即可

点击projects-》点击greenhat项目-》settings-》 Webhooks

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zCtVDxNv-1689070560539)(/media/202306/2023-06-26_152434_8029180.5982714735909546.png)]
把5.4.2定义的token拿过来,拼出url地址:http://172.16.10.16:7096/job/go-pipeline-demo/build?token=egonlao6

注意:

1、下图中的Secret Token之所以可以为空,是因为我们通过url地址问号后的内容传递了该token值

2、选中的Trigger代表在xxx事件发生时,会触发webhook对接jenkins的流水线,此处我们选择两个跟push有关的事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i1yuLs5F-1689070560539)(/media/202306/2023-06-26_152448_6181200.5919665759406758.png)]
点击Add webhook,在屏幕上面会出现一行粉字:Url is blocked: Requests to the local network are not allowed

则需要进入 GitLab首页,点击下图所示Admin Area-》Settings -> NetWork -> 勾选 Outbound requests,然后–》点击save changes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIoO2dO1-1689070560539)(/media/202306/2023-06-26_152457_8910290.023205952234381044.png)]
点击save changes之后,回到项目的webhooks界面点击测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHNG2Ory-1689070560540)(/media/202306/2023-06-26_152504_4150330.5577954675906976.png)]
点击Push events后会报一个403错误,需要做三件事

  • 1、需要进入jenkins的页面:http://172.16.10.16:7096/,、配置jenkins安全选项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bJMUplNa-1689070560540)(/media/202306/2023-06-26_152516_4963430.8751554966664858.png)]
系统管理-》全局安全配置->勾选匿名用户具有可读权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a519GUrh-1689070560540)(/media/202306/2023-06-26_152523_3013130.9213625805200839.png)]

  • 2、由于这里的 Jenkins 是比较新的 (2.346.1) 版本,该版本已经默认取消了 CSRF 的安全配置入口,所以我们需要手动执行一段脚本来禁用 CSRF 的跨站请求,系统管理 ->页面底部选择: 脚本命令行:执行下图所示的命令即可。
import jenkins.model.Jenkins
 
def jenkins = Jenkins.instance
 
jenkins.setCrumbIssuer(null)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPe0TDQR-1689070560540)(/media/202306/2023-06-26_152537_7283180.10775837580574243.png)]
如果在设置中-全局安全设置里面,如下图展示,则说明就关闭了CSRF

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvIyIHSM-1689070560540)(/media/202306/2023-06-26_152542_7347680.8620733742015023.png)]
也有一个方式了解一下:我们可以修改jenkins.yaml文件,为deployment增加一个env,然后重新部署jenkins.yaml

      env:
      - name: JAVA_OPTS
        value: "-Dhudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZDNoZJUN-1689070560541)(/media/202306/2023-06-26_152602_2526720.8991564258441188.png)]
3、系统管理-》插件管理-》安装GitLab插件

安装完毕后重启,在url地址后输入restart,即:http://172.16.10.16:7096/restart

然后点击系统管理-》系统配置-》找到gitlab,去掉勾选:Enable authentication for ‘/project’ end-point

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fi8lEpHg-1689070560541)(/media/202306/2023-06-26_152612_3475990.5316991356986788.png)]
然后再次点击test里的Push events,显示如下内容,代表手动触发push 事件成功,可以跑到jenkins里找到指定的流水线查看,已经开始工作,代表成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fa7pYxtr-1689070560541)(/media/202306/2023-06-26_152619_3862400.39386298716797896.png)]

6.4.4、创建Jenkinsfile文件

!!!!!!!!!强调:你自信看看Jekensfile文件的首字母是一个大写的字母哦,可不要创建错了!!!!!!!!!!!!!

6.4.4.1 思路概述

你可以在jenkins的web界面里编写pipeline的代码,但此处我们采用的方式是把pipeline的代码写到一个名为Jenkinsfile的文件,然后把该文件放到项目的根目录下,而且同一个项目的不同代码分支下都需要有这个Jenkinsfile文件,如此,一旦gitlab的webhook监听的事件发生,就会立刻通知指定的流水线,然后该流水线会按照配置好的信息去gitlab里拉取代码,然后去代码的根目录下找一个名为Jenkinsfile的文件,按照该文件里编写的流水线代码完成整个构建过程。

这里你可能会有几个问题

1、Jenkinsfile文件的首字母为何是大写的

2、Jenkinsfile文件为何要放在项目根目录下

这两问题都是二b才会问的问题,你好好瞅一眼5.1.3里我们当初在创建pipeline时,是我们自己规定的这个文件名,而且首字母写了一个大写的。

那么Jenkinsfile里应该如何编写呢?基本的逻辑就是

1、拉取代码

2、测试

3、构建(安装依赖包、编译等)

4、构建镜像

5、推送镜像到镜像仓库

6、编写k8s的yaml文件

7、kubectl更新yaml文件中的镜像地址与tag

从测试到更新 YAML 文件属于 CI 流程,后面部署属于 CD 的流程。

基本额pipeline模板如下

node('egon-test1') {
    stage('Clone') {
      echo "1.Clone Stage"
    }
    stage('Test') {
      echo "2.Test Stage"
    }
    stage('Build') {
      echo "3.Build Docker Image Stage"
    }
    stage('Push') {
      echo "4.Push Docker Image Stage"
    }
    stage('YAML') {
      echo "5.Change YAML File Stage"
    }
    stage('Deploy') {
      echo "6.Deploy Stage"
    }
}

注意我们在2.4小节的步骤4 里定义过一个pod Template,即slave pod的生成模板,该模板里启动pod引用的是一个镜像cnych/jenkins:jnlp6

也就说,如果我们真的按照上面写的流水线来的,node(‘egon-test1’) 也是选中用该镜像cnych/jenkins:jnlp6启动的一个slave pod来进行构建,如果我们要构建的是go程序,那么该镜像就不适用了,我们需要用一个具有go环境的镜像才可以。如果你真这么做了,看似可以,实则是一个坑。为什么呢?

因为除了go程序之外,你还有可能会构建java,构建python,那么你需要定制一个非常大的镜像,里面有所有你想要的环境,而且还要有git工具、docker工具、kubectl等工具,能想象到该镜像会变得多大了吧,而且很不灵活。

有没有更好的方式呢?有

就是我们先制作一个个小镜像/或者基于现成的也行

1、一个有go环境的小镜像

2、一个安装有docker的小镜像

3、一个安装有helm工具的小镜像

4、一个安装有kubectl工具的小镜像

然后我们可以删掉之前创建的pod Template

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zOLAHLm-1689070560541)(/media/202306/2023-06-26_152659_7905130.8474461306035092.png)]
然后在pipeline的代码里里动态生成pod template,在里面写好要引用的镜像 ,然后在每个stage里调用专门的镜像就可以,我们可以看到pod slave里会启动多个容器。

这么做的好处是,如果我们想构建另外一条用于构建java程序的流水线,那么我们只需要再创建了一个拥有java环境的小镜像就可以,至于2、3、4提到的镜像都是可以复用的

综上,在做好小镜像之后

1、针对java项目,我们就在java项目根目录下创建一个Jenkinsfile文件,在该文件里写入pipeline流水线代码

2、针对go项目,我们就在go项目根目录下创建一个Jenkinsfile文件,在该文件里写入pipeline流水线代码

3、针对go项目,我们就在python项目根目录下创建一个Jenkinsfile文件,在该文件里写入pipeline流水线代码

这三个Jenkinsfile文件涉及到的安装有docker工具的镜像、helm工具的镜像、kubectl工具的镜像都可以复用

6.4.4.2 准备工作之创建三个凭证

说明:我要要创建登录两套k8s集群的凭证、以及一个登录harbor镜像仓库的凭证,为啥要创建凭证呢?

构建完毕后一定会执行kubectl命令将构建结果提交到k8s集群里。我们可以将访问集群的 `kubeconfig` 文件拷贝到 kubectl 容器的 `~/.kube/config` 文件下面去,这样我们就可以在容器中访问 Kubernetes 集群了,但是由于我们构建是在 Slave Pod 中去构建的,Pod 就很有可能每次调度到不同的节点去,这就需要保证每个节点上有 `kubeconfig` 文件才能挂载成功,所以这里我们使用下面的方式。
 
获取测试环境与生产环境k8s集群的~/.kube/config文件,然后添加到jenkins里/创建凭证,这么做的目的是为了下一步我们创建pod Template里引用的镜像在执行kubectl命令时可以读取到凭证,读取到不同集群的凭证就可以登录到不同的集群

1、在jenins里创建一个登录k8s集群测试环境的凭证

# 测试环境k8s:172.16.10.14
cat /root/.kube/config

登录到jenkins里:主页面-》系统管理-》Manage Credentials->Stores scoped to jenkins下点击jenkins-》点击全局凭据-》点击添加凭据

如下图所示,或者干脆直接访问地址一步到位:http://172.16.10.16:7096/credentials/store/system/domain/_/newCredentials

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oCGPjlrq-1689070560541)(/media/202306/2023-06-26_152729_8543160.8280113559831603.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2QzaHLqU-1689070560542)(/media/202306/2023-06-26_152735_6136040.2053607280681936.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jExoJPvg-1689070560542)(/media/202306/2023-06-26_152742_5393180.39101570616228976.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U4I8i5pt-1689070560542)(/media/202306/2023-06-26_152748_1732090.7889879934493486.png)]

# 然后在 Jenkinsfile 的 kubectl 容器中读取上面添加的 Secret file 文件,拷贝到 ~/.kube/config 即可:
# 例如
stage('运行 Kubectl') {
  container('kubectl') {
    withCredentials([file(credentialsId: 'kubeconfig_ceshi', variable: 'KUBECONFIG')]) {
      echo "查看 K8S 集群 Pod 列表"
      sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
      sh "kubectl get pods"
    }
  }
}

2、同上,在jenins里创建一个登录k8s集群生产环境的凭证

# 生产环境k8s:172.16.10.15
cat /root/.kube/config

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3DfFlZ2-1689070560542)(/media/202306/2023-06-26_152809_2146260.07311410753303427.png)]
3、在jenins里创建一个登录harbor仓库的凭证

访问:http://172.16.10.16:7096/credentials/store/system/domain/_/newCredentials

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oVKkNPwk-1689070560542)(/media/202306/2023-06-26_152816_3309410.4475836188538851.png)]

6.4.4.3 准备工作之登录harbor创建一个存放go镜像的仓库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VuAtu3AF-1689070560542)(/media/202306/2023-06-26_152829_7311400.45183067469157834.png)]

6.4.4.4 准备工作之先在测试集群与生产集群都事先创建好test.yaml

运行在k8s中的实例

因为我们需要在k8s拉取harbor的私有仓库,需要用到账号密码,所以需要创建一个secret

kubectl create secret docker-registry registry-secret --namespace=default \
--docker-server=172.16.10.16:30002 --docker-username=admin  \
--docker-password=Harbor12345

test.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  labels:
    app: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: test
        image: 172.16.10.16:30002/goproject/greenhat:c2aa9e3  # 该镜像可以先填一个随便的,反正后期是要更新的
      imagePullSecrets:
      - name: registry-secret
6.4.4.5 准备工作之开发机push初始代码

1、开发机拉取代码

mkdir /mypro
cd /mypro/
 
git init
git config --global user.name "root"
git config --global user.email "email@example.com"
git remote add origin ssh://git@git.k8s.local:30022/root/greenhat.git
 
git pull origin master

2、编写go代码,推送到master分支

# 1、创建go.mod文件:vim go.mod
module golang
 
go 1.17
 
# 2、编写go代码:vim run.go
package main 
 
import ( 
    "fmt"
    "time"
) 
 
func main() { 
    fmt.Println("主分支.....") 
    time.Sleep(1000000 * time.Second) 
}
 
# 3、提交并push到master分支
git add .
git commit -m "提交run.go"
git push origin master

3、编写go代码,推送到develop分支

# 1、创建并切换到develop分支
[root@test07 mypro]# git branch
* master
[root@test07 mypro]# git checkout -b develop
Switched to a new branch 'develop'
[root@test07 mypro]# git branch
* develop
  master
[root@test07 mypro]# ls
go.mod  run.go
 
# 2、go.mod文件已经存在
 
# 3、编写go代码:vim run.go
package main 
 
import ( 
    "fmt"
    "time"
) 
 
func main() { 
    fmt.Println("开发分支.....") 
    time.Sleep(1000000 * time.Second) 
}
 
# 3
# 3、提交并push到master分支
git add .
git commit -m "提交run.go"
git push origin develop

上面两次push都会触发jenkins的流水线执行,但是会因为该项目的两个分支下都没有Jenkinsfile文件而执行失败

6.4.4.6 为该项目master分支创建Jenkinsfile文件并测试

1、在开发机,切换到项目根目录下,切换到主分支里,创建Jenkinsfile文件

切换到项目根目录下,切换到主分支里

[root@test07 mypro]# cd /mypro # 切换到项目根目录下
[root@test07 mypro]# 
[root@test07 mypro]# git checkout master # 切换到master分支下
切换到分支 'master'
[root@test07 mypro]# ls
go.mod  run.go

在项目根目录下编写Jenkinsfile文件,强调文件的首字母是一个大写的字母

// 定义pod slave的标签
def label = "slave-${UUID.randomUUID().toString()}"
 
// 定义动态生成的pod slave的模板,该pod启动时包含了3个容器名为golang、docker、kubectl
podTemplate(label: label, containers: [
  containerTemplate(name: 'golang', image: 'okteto/golang.1.17', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    // 获取开发任意git commit -m "xxx"指定的提交信息xxx
    def gitCommit = myRepo.GIT_COMMIT
    // 获取提交的分支
    def gitBranch = myRepo.GIT_BRANCH
    echo "------------>本次构建的分支是:${gitBranch}"
    // 仓库地址
    def registryUrl = "172.16.10.16:30002"
    def imageEndpoint = "goproject/greenhat"
 
    // 获取 git commit id 作为我们后面制作的docker镜像的tag
    def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
 
    // 镜像
    def image = "${registryUrl}/${imageEndpoint}:${imageTag}"
 
    stage('单元测试') {
      echo "1.测试阶段,此步骤略,可以根据需求自己定制"
    }
    stage('代码编译打包') {
      try {
        container('golang') {
          echo "2.代码编译打包阶段"
          sh """
            export GOPROXY=https://goproxy.cn
            GOOS=linux GOARCH=amd64 go build -v -o egongogo
            """
        }
      } catch (exc) {
        println "构建失败 - ${currentBuild.fullDisplayName}"
        throw(exc)
      }
    }
    stage('构建 Docker 镜像') {
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
        credentialsId: 'docker-auth',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASSWORD']]) {
          container('docker') {
            echo "3. 构建 Docker 镜像阶段"
sh '''
cat >Dockerfile<<EOF
FROM centos:8
USER root
COPY ./egongogo /opt/
RUN chmod +x /opt/egongogo
CMD /opt/egongogo
EOF'''
            sh """
              docker login ${registryUrl} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
              docker build -t ${image} .
              docker push ${image}
              """
          }
      }
    }
    stage('运行 Kubectl') {
      container('kubectl') {
        script {
            if ("${gitBranch}" == 'origin/master') {
              withCredentials([file(credentialsId: 'kubeconfig_shengchan', variable: 'KUBECONFIG')]) {
                 echo "查看生产 K8S 集群 Pod 列表"
                 sh 'echo "${KUBECONFIG}"'
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods"
                 sh "kubectl set image deployment/test test=${image}"
              }
            }else if("${gitBranch}" == 'origin/develop'){
              withCredentials([file(credentialsId: 'kubeconfig_ceshi', variable: 'KUBECONFIG')]) {
                 echo "查看测试 K8S 集群 Pod 列表"
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods -n kube-system"
                 sh "kubectl set image deployment test test=${image}"
              }
            }
        }
      } 
    }
  }
}

2、提交

git add .
git commit -m "master v1.0"
 
git push origin master # 一旦push完毕,会立刻引发gitlab的push事件,webhook会监听到该事件匹配,然后会利用配置好地址与token值找到jenkins具体某一条流水线触发其运行,本例中为go-pipeline-demo流水线,该流水线会先去gitlab里拉取master分支代码,然后加载该代码包根目录下的Jenkinsfile文件,按照文件规定完成构建与部署

3、查看jenkins

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-maiUnKHu-1689070560543)(/media/202306/2023-06-26_153000_1519520.53937353074671.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNktRYHJ-1689070560543)(/media/202306/2023-06-26_153005_2807770.865992132441408.png)]
4、master分支会部署到生产k8s集群,所以我们去生产集群查看

[root@test05 ~]# kubectl  get pods
NAME                    READY   STATUS    RESTARTS   AGE
test-76694586f5-c6v8v   1/1     Running   0          20s
[root@test05 ~]# kubectl  logs -f test-76694586f5-c6v8v
主分支.....
6.4.4.7 为该项目的develop分支创建Jenkinsfile文件并测试

基于上一小节的基础上 继续操作

1、切换分支

cd /mypro
git checkout branch

2、把上一小节的Jenkinsfile文件一模一样放一份到当前分支根目录下

3、提交、并push到develop分支

git add .
git commit -m "测试dev分支"
git push origin develop

4、jenkins查看构建情况,同上,略

5、因为我们推送的develop分支,会发布到测试集群,所以登录测试集群

[root@test04 ~]# kubectl  get pods
NAME                    READY   STATUS    RESTARTS   AGE
test-5d9cf946f9-pzt5p   1/1     Running   0          14s
[root@test04 ~]# kubectl logs -f test-5d9cf946f9-pzt5p 
开发分支.....

后续测试着玩

git checkout master
 
修改run.go的代码,然后提交
 
然后到jenkins里点击参数化构建,选择master分支,构建,会发现生产集群内容更新
 
git checkout develop
 
修改run.go的代码,然后提交
 
然后到jenkins里点击参数化构建,选择master分支,构建,会发现测试集群内容更新

6.5 优化之为发布到生产集群添加交互式确认

强调一句话:如果是生产环境肯定不能开发人员push完毕后就直接部署到生产k8s集群中,只有develop分支才会push完自动完成整套流程发布到测试号环境,所以真正在应用的时候,应该区分对待master分支与develop分支

方式一:

1、创建一条流水线,在配置git信息时,配置只针对develop分支,然后在webhook里指向该流水线的地址与token,如此,push了develop分支才会完成整套构建自动发布到测试环境中

2、创建另外一条流水线,在配置git信息时,配置只针对master分支,不为其创建webhook,其余操作与上面的都一样,这样每次发布的时候都手动点击构建即可

方式二:修改master与develop分支下的Jenkinfile文件(这俩文件是一样的),然后专门为部署到生产环境那一个stage加上交互式

交互式代码示例

stage('快速回滚?') {
      withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
        container('helm') {
          sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
          def userInput = input(
            id: 'userInput',
            message: '是否需要快速回滚?',
            parameters: [
                [
                    $class: 'ChoiceParameterDefinition',
                    choices: "Y\nN",
                    name: '回滚?'
                ]
            ]
          )
          if (userInput == "Y") {
            sh "helm rollback devops-demo --namespace kube-ops"
          }
        }
      }
    }

master分支下的Jenkinsfile文件

// 定义pod slave的标签
def label = "slave-${UUID.randomUUID().toString()}"
 
// 定义动态生成的pod slave的模板,该pod启动时包含了3个容器名为golang、docker、kubectl
podTemplate(label: label, containers: [
  containerTemplate(name: 'golang', image: 'okteto/golang.1.17', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    // 获取开发任意git commit -m "xxx"指定的提交信息xxx
    def gitCommit = myRepo.GIT_COMMIT
    // 获取提交的分支
    def gitBranch = myRepo.GIT_BRANCH
    echo "------------>本次构建的分支是:${gitBranch}"
    // 仓库地址
    def registryUrl = "172.16.10.16:30002"
    def imageEndpoint = "goproject/greenhat"
 
    // 获取 git commit id 作为我们后面制作的docker镜像的tag
    def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
 
    // 镜像
    def image = "${registryUrl}/${imageEndpoint}:${imageTag}"
 
    stage('单元测试') {
      echo "1.测试阶段,此步骤略,可以根据需求自己定制"
    }
    stage('代码编译打包') {
      try {
        container('golang') {
          echo "2.代码编译打包阶段"
          sh """
            export GOPROXY=https://goproxy.cn
            GOOS=linux GOARCH=amd64 go build -v -o egongogo
            """
        }
      } catch (exc) {
        println "构建失败 - ${currentBuild.fullDisplayName}"
        throw(exc)
      }
    }
    stage('构建 Docker 镜像') {
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
        credentialsId: 'docker-auth',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASSWORD']]) {
          container('docker') {
            echo "3. 构建 Docker 镜像阶段"
sh '''
cat >Dockerfile<<EOF
FROM centos:8
USER root
COPY ./egongogo /opt/
RUN chmod +x /opt/egongogo
CMD /opt/egongogo
EOF'''
            sh """
              docker login ${registryUrl} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
              docker build -t ${image} .
              docker push ${image}
              """
          }
      }
    }
 
    stage('运行 Kubectl') {
      container('kubectl') {
        script {
            if ("${gitBranch}" == 'origin/master') {
              withCredentials([file(credentialsId: 'kubeconfig_shengchan', variable: 'KUBECONFIG')]) {
                 echo "查看生产 K8S 集群 Pod 列表"
                 sh 'echo "${KUBECONFIG}"'
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods"
 
                 // 添加交互代码,确认是否要部署到生产环境
                 def userInput = input(
                    id: 'userInput',
                    message: '是否确认部署到生产环境?',
                    parameters: [
                        [
                            $class: 'ChoiceParameterDefinition',
                            choices: "Y\nN",
                            name: '是否确认部署到生产环境?'
                        ]
                    ]
                  )
                  if (userInput == "Y") {
                    // 部署到生产环境 
                    sh "kubectl set image deployment/test test=${image}"
                  }else {
                    // 任务结束
                    echo "取消本次任务"
                  }
 
              }
            }else if("${gitBranch}" == 'origin/develop'){
              withCredentials([file(credentialsId: 'kubeconfig_ceshi', variable: 'KUBECONFIG')]) {
                 echo "查看测试 K8S 集群 Pod 列表"
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods -n kube-system"
                 sh "kubectl set image deployment test test=${image}"
              }
            }
        }
      } 
    }
 
  }
}

然后在开发机切换到master分支,修改一下run.go的内容,然后push到master分支,然后去jenkins里查看

ps:一定要注意,你必须事先以管理员账号身份登录到jenkins里,选择Y或者N才有效,未登录状态或者没有权限你可以可以看也可以选,但是你点击继续,就会报错:You need to have Job/Build permissions to submit this.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3DNHmob-1689070560543)(/media/202306/2023-06-26_153115_2674780.8723783674776888.png)]
更进一步,我们可以为master与develop都加入回滚功能,示例代码

    stage('快速回滚?') {
      withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
        container('helm') {
          sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
          def userInput = input(
            id: 'userInput',
            message: '是否需要快速回滚?',
            parameters: [
                [
                    $class: 'ChoiceParameterDefinition',
                    choices: "Y\nN",
                    name: '回滚?'
                ]
            ]
          )
          if (userInput == "Y") {
            sh "helm rollback devops-demo --namespace kube-ops"
          }
        }
      }
    }

修改两个分支下的Jenkinsfile文件,添加回滚代码,如下所示,然后为部署到生产与测试环境的部分都加上加入回滚功能

// 定义pod slave的标签
def label = "slave-${UUID.randomUUID().toString()}"
 
// 定义动态生成的pod slave的模板,该pod启动时包含了3个容器名为golang、docker、kubectl
podTemplate(label: label, containers: [
  containerTemplate(name: 'golang', image: 'okteto/golang.1.17', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    // 获取开发任意git commit -m "xxx"指定的提交信息xxx
    def gitCommit = myRepo.GIT_COMMIT
    // 获取提交的分支
    def gitBranch = myRepo.GIT_BRANCH
    echo "------------>本次构建的分支是:${gitBranch}"
    // 仓库地址
    def registryUrl = "172.16.10.16:30002"
    def imageEndpoint = "goproject/greenhat"
 
    // 获取 git commit id 作为我们后面制作的docker镜像的tag
    def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
 
    // 镜像
    def image = "${registryUrl}/${imageEndpoint}:${imageTag}"
 
    stage('单元测试') {
      echo "1.测试阶段,此步骤略,可以根据需求自己定制"
    }
    stage('代码编译打包') {
      try {
        container('golang') {
          echo "2.代码编译打包阶段"
          sh """
            export GOPROXY=https://goproxy.cn
            GOOS=linux GOARCH=amd64 go build -v -o egongogo
            """
        }
      } catch (exc) {
        println "构建失败 - ${currentBuild.fullDisplayName}"
        throw(exc)
      }
    }
    stage('构建 Docker 镜像') {
      withCredentials([[$class: 'UsernamePasswordMultiBinding',
        credentialsId: 'docker-auth',
        usernameVariable: 'DOCKER_USER',
        passwordVariable: 'DOCKER_PASSWORD']]) {
          container('docker') {
            echo "3. 构建 Docker 镜像阶段"
sh '''
cat >Dockerfile<<EOF
FROM centos:8
USER root
COPY ./egongogo /opt/
RUN chmod +x /opt/egongogo
CMD /opt/egongogo
EOF'''
            sh """
              docker login ${registryUrl} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
              docker build -t ${image} .
              docker push ${image}
              """
          }
      }
    }
    stage('运行 Kubectl') {
      container('kubectl') {
        script {
            if ("${gitBranch}" == 'origin/master') {
              withCredentials([file(credentialsId: 'kubeconfig_shengchan', variable: 'KUBECONFIG')]) {
                 echo "查看生产 K8S 集群 Pod 列表"
                 sh 'echo "${KUBECONFIG}"'
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods"
 
                 // 添加交互代码,确认是否要部署到生产环境
                 def userInput = input(
                    id: 'userInput',
                    message: '是否确认部署到生产环境?',
                    parameters: [
                        [
                            $class: 'ChoiceParameterDefinition',
                            choices: "Y\nN",
                            name: '是否确认部署到生产环境?'
                        ]
                    ]
                  )
                  if (userInput == "Y") {
                    // 部署到生产环境 
                    sh "kubectl set image deployment/test test=${image}"
                  }else {
                    // 任务结束
                    echo "取消本次任务"
                  }
 
                  // 加入回滚功能,注意变量名不能与上面的冲突
                  def userInput2 = input(
                    id: 'userInput',
                    message: '是否需要快速回滚?',
                    parameters: [
                        [
                            $class: 'ChoiceParameterDefinition',
                            choices: "Y\nN",
                            name: '回滚?'
                        ]
                    ]
                  )
                  if (userInput2 == "Y") {
                    sh "kubectl rollout undo deployment/test"
                  }
 
              }
            }else if("${gitBranch}" == 'origin/develop'){
              withCredentials([file(credentialsId: 'kubeconfig_ceshi', variable: 'KUBECONFIG')]) {
                 echo "查看测试 K8S 集群 Pod 列表"
                 sh 'mkdir -p ~/.kube && /bin/cp "${KUBECONFIG}" ~/.kube/config'
                 sh "kubectl get pods -n kube-system"
                 sh "kubectl set image deployment test test=${image}"
 
                 // 加入回滚功能
                 def userInput2 = input(
                   id: 'userInput',
                   message: '是否需要快速回滚?',
                   parameters: [
                       [
                           $class: 'ChoiceParameterDefinition',
                           choices: "Y\nN",
                           name: '回滚?'
                       ]
                   ]
                 )
                 if (userInput2 == "Y") {
                   sh "kubectl rollout undo deployment/test"
                 }
 
              }
            }
        }
      } 
    }
 
  }
}

测试master分支与develop分支的提交

6.6 总结Jenkinsfile的大致模板

# 基本流程就是:Clone 代码 -> 单元测试 -> Golang 编译打包 -> Docker 镜像构建/推送 -> Kubectl 部署服务。
def label = "slave-${UUID.randomUUID().toString()}"
 
podTemplate(label: label, containers: [
  containerTemplate(name: 'golang', image: 'okteto/golang.1.17', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout scm
    def gitCommit = myRepo.GIT_COMMIT
    def gitBranch = myRepo.GIT_BRANCH
 
    stage('单元测试') {
      echo "测试阶段"
    }
    stage('代码编译打包') {
      container('golang') {
        echo "代码编译打包阶段"
      }
    }
    stage('构建 Docker 镜像') {
      container('docker') {
        echo "构建 Docker 镜像阶段"
      }
    }
    stage('运行 Kubectl') {
      container('kubectl') {
        echo "查看 K8S 集群 Pod 列表"
        sh "kubectl get pods"
      }
    }
  }
}
 
直接在 `podTemplate` 里面定义每个阶段需要用到的容器,volumes 里面将我们需要用到的 `docker.sock` 文件,需要注意的我们使用的 label 标签是是一个随机生成的,这样有一个好处就是有多个任务来的时候就可以同时构建了。

http://git.k8s.local:1180/root/greenhat/hooks/3/edit

http://172.16.10.16:30002/harbor/projects

http://172.16.10.16:7096/

jenkins安装插件安装插件:没有用的插件,可以考虑删掉

gitlab branch source

multibranch-scan-webhook-trigger

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值