Bootstrap

搭建Docker+K8S+GitLab+Jenkins+Harbor持续集成交付Springboot环境

作为一个新入行的小白,参考了网上众多资料,也学到了很多知识,但搭建这样一套环境还是踩了不少坑。
当然,对于实际生成环境还有更多的坑要踩,目前这套环境仅在测试环境中跑通,还有很多细节需要完善,在此记录,仅当作自己学习的笔记。也欢迎爱好技术的同学共同交流,如有不对之处,欢迎大家指出。

1.概述

CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在集成新代码时所引发的问题。

具体而言,CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为“CI/CD 管道”,由开发和运维团队以敏捷方式协同支持。

本案根据研发团队要求,采用Jenkins+GitLab+Docker+Kubernetes实现可持续自动化微服务。

1.1 Gitlab简介

GitLab是由 GitLab Inc.开发,一款基于Git的完全集成的软件开发平台(fully集成软件development platform)。与Github的区别在于可以免费且任意的创建私有仓库,并且可将服务部署在内网,通过管理人员配置的账号密码和访问权限来管理项目及代码,确保代码安全。

1.2 Jenkins简介

Jenkins是一款由Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目复刻。

Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中(例如Apache Tomcat)。它支持软件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和RTC),可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令。Jenkins的主要开发者是川口耕介。Jenkins是在MIT许可证下发布的自由软件。

可以通过各种手段触发构建。例如提交给版本控制系统时被触发,也可以通过类似Cron的机制调度,也可以在其他的构建已经完成时,还可以通过一个特定的URL进行请求。

1.3 Docker

Docker是一个开放源代码软件,是一个开放平台,用于开发应用、交付应用、运行应用。Docker允许用户将基础设施(Infrastructure)中的应用单独分割出来,形成更小的颗粒(容器),从而提高交付软件的速度。

Docker容器与虚拟机类似,但二者在原理上不同。容器是将操作系统层虚拟化,虚拟机则是虚拟化硬件,因此容器更具有便携性、高效地利用服务器。容器更多的用于表示软件的一个标准化单元。由于容器的标准化,因此它可以无视基础设施(Infrastructure)的差异,部署到任何一个地方。另外,Docker也为容器提供更强的业界的隔离兼容。

本案通过Docker实现微服务的部署。

1.4 Kubernetes

Kubernetes(常简称为K8s)是用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。该系统由Google设计并捐赠给Cloud Native Computing Foundation(今属Linux基金会)来使用。

它旨在提供“跨主机集群的自动部署、扩展以及运行应用程序容器的平台”。它支持一系列容器工具,包括Docker等。

1.5 Harbor

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必须的功能特性,例如安全、标识和管理等,扩展了开源Dockerfile Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中,确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如管理、访问控制和活动审计等。

1.6 Spring boot

SpringBoot是一种全新的框架,目的是为了简化Spring应用的初始搭建以及开发过程。该框架使用特定的方式(集成starter,约定优于配置)来进行配置,从而使开发人员不需要再定义样板化的配置。SpringBoot提供了一种新的编程范式,可以更加快速便捷地开发Spring项目,在开发过程当中可以专注于应用程序本身的功能开发,而无需在Spring配置上花太大的工夫。

SpringBoot基于Sring4进行设计,继承了原有Spring框架的优秀基因。SpringBoot并不是一个框架,而是一些类库的集合。maven或者gradle项目导入相应依赖即可使用SpringBoot,而无需自行管理这些类库的版本。

2.环境说明

2.1 系统架构

在这里插入图片描述
图 1 系统架构图

研发人员通过Git将代码上传至Gitlab,项目管理员审核代码后合并代码;

Jenkins通过webhook获取Gitlab上的项目代码,并通过maven打包构建镜像,如果构建报错,则可以通过邮件或钉钉等通知相关人员;

镜像构建完成后,Jenkins自动将镜像pull到Harbor镜像仓库;

Harbor镜像仓库通过将镜像push到k8s集群运行,k8s通过编排管理,实现镜像服务的伸缩和高可用。

2.2 软硬件环境

实际生产环境根据需要配置相应服务器,本文为简化操作,使用4台虚拟机,系统版本为CentOS7.9.2009,配置为4核8G,详情如下:

表 1 系统基本环境表

HostnameIP备注
master10.1.1.129/
node110.1.1.130/
node210.1.1.131/
k8s-harbor10.1.1.132安装jenkins、harbor、nfs

表 2 软件版本表

软件版本项目地址
操作系统CentOS7.9.2009https://www.centos.org/
JDK1.8https://www.oracle.com/java/technologies/javase-downloads.html
Maven3.6.3https://maven.apache.org/
dockerclient 19.03.9/docker engine 19.03.9https://www.docker.com/
docker-compose1.28.4https://github.com/docker/compose/releases/
Kubernetes1.20.4https://kubernetes.io/
GitlabCommunity Edition 12.1.6https://about.gitlab.com/
Jenkins2.263.4https://www.jenkins.io/
Harborv2.0.6-f5884625https://goharbor.io/

3.安装配置过程

3.1基础环境配置

在所有节点操作

1.系统centos7.9 关闭selinux、防火墙

sudo systemctl disable firewalld
sudo systemctl stop firewalld
sed -i “s/SELINUX=enforcing/SELINUX= disabled/”/etc/selinux/config

2.修改yum源至阿里云

curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

3.安装vim lrzsz bash-completion wget nfs-utils等常用软件

sudo yum install lrzsz bash-completion wget nfs-utils

4.yum update 然后重启

sudo yum update
sudo reboot

5.时间同步

配置 ‘/etc/chrony.conf’ 文件的内容为:

server ntp.aliyun.com iburst
stratumweight 0
driftfile /var/lib/chrony/drift
rtcsync
makestep 10 3
bindcmdaddress 127.0.0.1
bindcmdaddress ::1
keyfile /etc/chrony.keys
commandkey 1
generatecommandkey
logchange 0.5
logdir /var/log/chrony

保存退出

重启chronyd服务

sudo systemctl restart chronyd 

6.配置互相免密登陆:

修改/etc/hosts

添加

[master ip] master的name

[node1 ip] node1的name

[node2 ip] node2的name

如:

10.1.1.129   master
10.1.1.130   node1
10.1.1.131   node2

在每个节点家目录下:

mkdir .ssh 
ssh-keygen -t rsa 
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

然后将node节点的公钥给master,在master上导入node节点的公钥

node1#scp node1.pub root@master:/root/.ssh/node1
node2#scp node2.pub root@master:/root/.ssh/node2
master#cat /root/.ssh/node1 >> ~/.ssh/authorized_keys
master#cat /root/.ssh/node1 >> ~/.ssh/authorized_keys
master#scp ~/.ssh/authorized_keys root@node1:/root/.ssh/authorized_keys
master#scp ~/.ssh/authorized_keys root@node2:/root/.ssh/authorized_keys

7.每个节点安装jdk

yum install java-1.8.0-openjdk* -y

配置环境变量,修改/etc/profile文件,在最后添加:

JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.282.b08-1.el7_9.x86_64/  ##这里的路径注意使用你安装的jdk的版本号
CLASS_PATH=.:$JAVA_HOME/lib
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASS_PATH PATH

保存退出后执行:

source /etc/profile

8.安装maven

官网下载,2021年3月最新 apache-maven-3.6.3-bin.tar.gz,下载后执行:

tar -zxvf apache-maven-3.6.3-bin.tar.gz
mv apache-maven-3.6.3 /usr/local/

配置环境变量:

添加

MAVEN_HOME=/usr/local/apache-maven-3.6.3

修改

PATH=$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASS_PATH MAVEN_HOME PATH

保存退出后执行:

source /etc/profile

创建maven下载文件夹

mkdir /home/repository

修改文件夹权限,否则jenkins构建时无法下载,报错

chown -R jenkins:jenkins /home/repository/

修改配置文件

vim /usr/local/apache-maven-3.6.3/conf/settings.xml

55行处添加

<localRepository>/home/repository</localRepository>

修改国内源,打开 Maven 的配置文件(windows机器一般在maven安装目录的conf/settings.xml),在(在152行)标签中添加 mirror 子节点:

<mirror>
  <id>aliyunmaven</id>
  <mirrorOf>*</mirrorOf>
  <name>阿里云公共仓库</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

9.安装docker,使用阿里源,当前2021年2月22日,k8s仅支持到docker19.03版本

sudo yum install -y yum-utils device-mapper-persistent-data lvm2

添加软件源信息

sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo
yum install https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.13-3.1.el7.x86_64.rpm

更新并安装Docker-CE

sudo yum makecache fast
sudo yum -y install docker-ce-19.03.9 docker-ce-cli-19.03.9

配置阿里云镜像加速

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://[1234].mirror.aliyuncs.com"]  #[1234]部分替换成自己的阿里云容器镜像加速地址
  }
  EOF

开启Docker服务

sudo systemctl enable docker
sudo systemctl start docker

10.安装docker-compose

项目地址https://github.com/docker/compose/releases

选择对应版本下载后移动至/usr/local/bin/文件夹下

mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose

添加执行权限

chmod a+x /usr/local/bin/docker-compose

11.安装k8s基础环境

关闭 swap分区

swapoff -a
yes | cp /etc/fstab /etc/fstab_bak
cat /etc/fstab_bak |grep -v swap > /etc/fstab

修改 /etc/sysctl.conf

# 如果有配置,则修改

sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g" /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.disable_ipv6.*#net.ipv6.conf.all.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.default.disable_ipv6.*#net.ipv6.conf.default.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.lo.disable_ipv6.*#net.ipv6.conf.lo.disable_ipv6=1#g" /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.forwarding.*#net.ipv6.conf.all.forwarding=1#g" /etc/sysctl.conf

# 可能没有,追加

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf

# 执行命令以应用

sysctl -p

配置K8S的yum源

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

# ps: 由于官网未开放同步方式, 可能会有索引gpg检查失败的情况, 这时请用 yum install -y --nogpgcheck kubelet kubeadm kubectl 安装

# 卸载旧版本并安装新版K8S

yum remove -y kubelet kubeadm kubectl
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet

3.2初始化k8s的master节点

1.用一个脚本搞定

# Kubernetes 容器组所在的网段,该网段安装完成后,由 kubernetes 创建,事先并不存在于物理网络中:

#!/bin/bash
# 脚本出错时终止执行
set -e

read -p "MASTER_IP=" MASTER_IP       #master节点的ip
read -p "APISERVER_NAME=" APISERVER_NAME   #master和node节点在初始化时,该项要保持一致,该项为k8s集群的dnsname,需要导入到/etc/hosts中
read -p "POD_SUBNET=" POD_SUBNET       #master和node节点在初始化时,该项要保持一致,Kubernetes 容器组所在的网段,该网段安装完成后,由 kubernetes 创建,事先并不存在于物理网络中

echo "${MASTER_IP}  ${APISERVER_NAME}" >> /etc/hosts
if [ ${#POD_SUBNET} -eq 0 ] || [ ${#APISERVER_NAME} -eq 0 ]; then
   echo -e "\033[31;1m请确保您已经设置了环境变量 POD_SUBNET 和 APISERVER_NAME \033[0m"
   echo 当前POD_SUBNET=$POD_SUBNET
   echo 当前APISERVER_NAME=$APISERVER_NAME
   exit 1
fi

# 更多内容,请查看:

https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2

#k8s的文件路径请自己指定

mkdir /k8s        
rm -f /k8s/kubeadm-config.yaml    
cat <<EOF > /k8s/kubeadm-config.yaml  
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.20.4   #此处需替换为你安装的k8s版本号
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
controlPlaneEndpoint: "${APISERVER_NAME}:6443"
networking:
serviceSubnet: "10.96.0.0/16"
podSubnet: "${POD_SUBNET}"
dnsDomain: "cluster.local"
EOF

kubeadm init
# 根据服务器网速的情况,您需要等候 3 - 10 分钟
kubeadm init --config=/k8s/kubeadm-config.yaml --upload-certs  
# 配置 kubectl
rm -rf /root/.kube/
mkdir /root/.kube/
cp -i /etc/kubernetes/admin.conf /root/.kube/config
# 安装 calico 网络插件
# 参考文档 https://docs.projectcalico.org/v3.13/getting-started/kubernetes/self-managed-onprem/onpremises

echo "安装calico-3.13.1"
cd /k8s
rm -f calico-3.13.1.yaml
wget https://kuboard.cn/install-script/calico/calico-3.13.1.yaml
kubectl apply -f calico-3.13.1.yaml
2. 执行如下命令,等待 3-10 分钟,直到所有的容器组处于 Running 状态
 watch kubectl get pod -n kube-system -o wide

查看 Master 节点初始化结果

kubectl get nodes -o wide

status为READY说明初始化成功

3.初始化后输出中的

kubeadm join k8s.master:6443 --token k7r8hy.us6yyg6cx9qz9dw1 \
  --discovery-token-ca-cert-hash sha256:2980589c84da61dcac22f1e0d2dd11d2c4331e2ac0951f1ae2bcccb2c397360c \
  --control-plane --certificate-key 00df25ba37c22591d93fb3336cd11f7d7a1bbe58d7c068940b0dd10a88251e70

# 可以用来将node节点加入,也可以后续再用命令生成,但需注意k8s.master要在node节点中的/etc/hosts中,且替换为你自己的设置

3.3初始化k8s的node节点

先在master节点获取token

kubeadm token create --print-join-command

# 只在 worker 节点执行

# 本例中10.1.1.129 为 master 节点的内网 IP

 export MASTER_IP=10.1.1.129

# 替换 k8s.master 为初始化 master 节点时所使用的 APISERVER_NAME

  export APISERVER_NAME=k8s.master
  echo "${MASTER_IP}  ${APISERVER_NAME}" >> /etc/hosts

# 替换为 master 节点上 kubeadm token create 命令输出的join

如:kubeadm join k8s.master:6443 --token k7r8hy.us6yyg6cx9qz9dw1 \

–discovery-token-ca-cert-hash sha256:2980589c84da61dcac22f1e0d2dd11d2c4331e2ac0951f1ae2bcccb2c397360c \

–control-plane --certificate-key 00df25ba37c22591d93fb3336cd11f7d7a1bbe58d7c068940b0dd10a88251e70

在master节点执行

 kubectl get node 

状态为READY说明node节点加入成功,可能要等1-3分钟才能启动

3.4 k8s安装ingress-nginx

Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。

Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。

在master节点执行,在你的k8s目录中创建下面的文件,如本例中的/k8s,并注意yaml文件的缩进格式

1.创建ingress-nginx命名空间

​ 创建ingress-nginx-namespace.yaml文件,文件内容如下所示。

 apiVersion: v1
      kind: Namespace
      metadata:
      name: ingress-nginx
      labels:
        name: ingress-nginx

​ 执行如下命令创建ingress-nginx命名空间。

  kubectl apply -f ingress-nginx-namespace.yaml

2.安装ingress controller

​ 创建ingress-nginx-mandatory.yaml文件,文件内容如下所示。

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: default-http-backend
  labels:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx
  namespace: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: default-http-backend
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: default-http-backend
        app.kubernetes.io/part-of: ingress-nginx
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: default-http-backend
          # Any image is permissible as long as:
          # 1. It serves a 404 page at /
          # 2. It serves 200 on a /healthz endpoint
          image: registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/defaultbackend-amd64:1.5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 30
            timeoutSeconds: 5
          ports:
            - containerPort: 8080
          resources:
            limits:
              cpu: 10m
              memory: 20Mi
            requests:
              cpu: 10m
              memory: 20Mi

---
apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller
          image: registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller:0.20.0
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1

---

执行如下命令安装ingress controller。

kubectl apply -f ingress-nginx-mandatory.yaml

3.安装K8S SVC:ingress-nginx

​ 主要是用来用于暴露pod:nginx-ingress-controller。

​ 创建service-nodeport.yaml文件,文件内容如下所示。

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30080
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
      nodePort: 30443
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

​ 执行如下命令安装。

  kubectl apply -f service-nodeport.yaml

4.访问K8S SVC:ingress-nginx

​ 查看ingress-nginx命名空间的部署情况,如下所示。

kubectl get pod -n ingress-nginx

​ 在命令行服务器命令行输入如下命令查看ingress-nginx的端口映射情况。

kubectl get svc -n ingress-nginx

​ 所以,可以通过Master节点的IP地址和30080端口号来访问ingress-nginx,如下所示。

 curl 10.1.1.129:30080    

​ default backend - 404

​ 也可以在浏览器打开http://10.1.1.129:30080 来访问ingress-nginx

3.5 k8s安装gitlab代码仓库(master节点执行)

1.创建k8s-ops命名空间

创建k8s-ops-namespace.yaml文件,文件内容如下所示。

apiVersion: v1
kind: Namespace
metadata:
  name: k8s-ops
  labels:
    name: k8s-ops

执行如下命令创建命名空间。

kubectl apply -f k8s-ops-namespace.yaml 

2.安装gitlab-redis

创建gitlab-redis.yaml文件,文件的内容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: k8s-ops
  labels:
    name: redis
spec:
  selector:
    matchLabels:
      name: redis
  template:
    metadata:
      name: redis
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        image: sameersbn/redis
        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: 10
          timeoutSeconds: 5
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/redis

---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: k8s-ops
  labels:
    name: redis
spec:
  ports:
    - name: redis
      port: 6379
      targetPort: redis
  selector:
    name: redis

首先,在命令行执行如下命令创建/data1/docker/xinsrv/redis目录。

 mkdir -p /data1/docker/xinsrv/redis

执行如下命令安装gitlab-redis。

 kubectl apply -f gitlab-redis.yaml 

3.安装gitlab-postgresql

创建gitlab-postgresql.yaml,文件内容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql
  namespace: k8s-ops
  labels:
    name: postgresql
spec:
  selector:
    matchLabels:
      name: postgresql
  template:
    metadata:
      name: postgresql
      labels:
        name: postgresql
    spec:
      containers:
      - name: postgresql
        image: sameersbn/postgresql:12-20200524
        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
        ports:
        - name: postgres
          containerPort: 5432
        volumeMounts:
        - mountPath: /var/lib/postgresql
          name: data
        livenessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/postgresql
---
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: k8s-ops
  labels:
    name: postgresql
spec:
  ports:
    - name: postgres
      port: 5432
      targetPort: postgres
  selector:
    name: postgresql

首先,执行如下命令创建/data1/docker/xinsrv/postgresql目录。

mkdir -p /data1/docker/xinsrv/postgresql

接下来,安装gitlab-postgresql,如下所示。

kubectl apply -f gitlab-postgresql.yaml

4.安装gitlab

(1)配置用户名和密码

​ 首先,在命令行使用base64编码为用户名和密码进行转码,本示例中,使用的用户名为admin,密码为admin12345转码情况如下所示。

[root@master]# echo -n 'admin' | base64 
YWRtaW4=
[root@master]# echo -n 'admin12345' | base64 
YWRtaW4xMjM0NQ==

​ 转码后的用户名为:YWRtaW4= 密码为:YWRtaW4xMjM0NQ==

​ 接下来,创建secret-gitlab.yaml文件,主要是用户来配置GitLab的用户名和密码,文件内容如下所示。

apiVersion: v1
kind: Secret
metadata:
  namespace: k8s-ops
  name: git-user-pass
type: Opaque
data:
  username: YWRtaW4=
  password: YWRtaW4xMjM0NQ==

​ 执行配置文件的内容,如下所示。

kubectl create -f secret-gitlab.yaml

(2)安装GitLab

​ 创建gitlab.yaml文件,文件的内容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab
  namespace: k8s-ops
  labels:
    name: gitlab
spec:
  selector:
    matchLabels:
      name: gitlab
  template:
    metadata:
      name: gitlab
      labels:
        name: gitlab
    spec:
      containers:
      - name: gitlab
        image: sameersbn/gitlab:12.1.6
        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
          valueFrom:
            secretKeyRef:
              name: git-user-pass
              key: password
        - name: GITLAB_ROOT_EMAIL
          value: [email protected]      ##替换为你的邮箱地址
        - name: GITLAB_HOST
          value: gitlab.binghe.com		##替换为你的主机域名
        - name: GITLAB_PORT
          value: "80"
        - name: GITLAB_SSH_PORT
          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
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 180
          timeoutSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/gitlab
---
apiVersion: v1
kind: Service
metadata:
  name: gitlab
  namespace: k8s-ops
  labels:
    name: gitlab
spec:
  ports:
    - name: http
      port: 80
      nodePort: 30088
    - name: ssh
      port: 22
      targetPort: ssh
      nodePort: 30022
  type: NodePort
  selector:
    name: gitlab

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: gitlab
  namespace: k8s-ops
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  rules:
  - host: gitlab.binghe.com		##替换为你的主机域名
    http:
      paths:
      - backend:
          serviceName: gitlab
          servicePort: http

​ 注意:在配置GitLab时,监听主机时,不能使用IP地址,需要使用主机名或者域名,本例中使用的是gitlab.binghe.com主机名。

​ 在命令行执行如下命令创建/data1/docker/xinsrv/gitlab目录。

mkdir -p /data1/docker/xinsrv/gitlab

​ 安装GitLab,如下所示。

kubectl apply -f gitlab.yaml

5.安装完成

查看k8s-ops命名空间部署情况,如下所示。

[root@master]# kubectl get pod -n k8s-ops
NAME             READY  STATUS  RESTARTS  AGE
gitlab-7b459db47c-5vk6t    1/1   Running  0     11s
postgresql-79567459d7-x52vx  1/1   Running  0     30m
redis-67f4cdc96c-h5ckz    1/1   Running  1     10h

接下来,查看GitLab的端口映射,如下所示。

[root@master]# kubectl get svc -n k8s-ops
NAME     TYPE    CLUSTER-IP   EXTERNAL-IP  PORT(S)           AGE
gitlab    NodePort  10.96.153.100  <none>    80:30088/TCP,22:30022/TCP  2m42s
postgresql  ClusterIP  10.96.203.119  <none>    5432/TCP          32m
redis    ClusterIP  10.96.107.150  <none>    6379/TCP          10h

此时,可以看到,可以通过Master节点的主机名和端口30088就能够访问GitLab。如果是虚拟机搭建的测试环境,则需要查看gitlab的pod运行在哪个节点,然后用node节点ip+端口访问。

本例中,登陆时用户名为root,密码为admin12345。注意:这里的用户名是root而不是admin,因为root是GitLab默认的超级用户。

到此,K8S安装gitlab完成。

3.6 物理机安装gitlab(和3.5二选一即可)

1.安准基础依赖

sudo yum install -y curl policycoreutils-python openssh-server

2.安装Postfix
Postfix是一个邮件服务器,GitLab发送邮件需要用到

sudo yum install -y postfix
sudo systemctl enable postfix
sudo systemctl start postfix

3.安装gitlab
添加GitLab社区版Package

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash

​ 安装GitLab社区版

sudo yum install -y gitlab-ce

4.配置GitLab站点Url
GitLab默认的配置文件路径是/etc/gitlab/gitlab.rb
默认的站点Url配置项是:
external_url ‘http://gitlab.example.com’
这里我将GitLab站点Url修改为http://git.ken.io
也可以用IP代替域名,这里根据自己需求来即可
5.启动并访问GitLab

​ 通过你配置的域名或ip地址访问gitlab
​ 这时候会提示为管理员账号设置密码。管理员账号默认username是root。
​ 设置完成之后即可使用root账号登录,登陆后会进入欢迎界面。

sudo gitlab-ctl reconfigure
#完成后将会看到如下输出
Running handlers complete
Chef Client finished, 432/613 resources updated in 03 minutes 43 seconds
gitlab Reconfigured!

​ 通过你配置的域名或ip地址访问gitlab
​ 这时候会提示为管理员账号设置密码。管理员账号默认username是root。
​ 设置完成之后即可使用root账号登录,登陆后会进入欢迎界面。

3.7 安装Harbor私有仓库

安装前需要安装docker环境,可以选择在线或离线安装
项目地址https://github.com/goharbor/harbor/releases
根据需要下载对应版本
1.下载Harbor的离线安装版本

wget https://github.com/goharbor/harbor/releases/download/v2.0.6/harbor-offline-installer-v2.0.6.tgz

2.解压Harbor的安装包

tar -zxvf harbor-offline-installer-v2.0.6.tgz

解压成功后,会在服务器当前目录生成一个harbor目录。

3.配置Harbor
(1)修改harbor.yml文件,如果该文件不存在,则从harbor.yml.tmpl复制一下
修改的配置项如下所示。

hostname: 10.1.1.132    ###修改成自己的主机ip,如果在公网,则添加能访问到该主机的域名
http:
port: 1180                ###默认是80端口,可以修改成自己想设置的端口
harbor_admin_password: 123456		###修改为你要设置的密码
###并把https注释掉,不然在安装的时候会报错:ERROR:root:Error: The protocol is https but attribute ssl_cert is not set
#https:
#port: 443
#certificate: /your/certificate/path
#private_key: /your/private/key/path

​ (2)修改daemon.json文件
​ 在需要访问harbor的所有主机上,修改/etc/docker/daemon.json文件,没有的话就创建,在/etc/docker/daemon.json文件中添加如下内容。

[root@master~]# cat /etc/docker/daemon.json
        {
        "registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"], ##修改为你的容器加速器地址
        "insecure-registries":["10.1.1.132:1180"]   ##修改为你的harbor服务器地址和端口
        }

4.安装并启动harbor
配置完成后,输入如下命令即可安装并启动Harbor

./install.sh 

5.harbor开机启动
编辑/lib/systemd/system/harbor.service

[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  [harbor dir]/docker-compose.yml up      ##中括号部分修改成harbor的文件夹
ExecStop=/usr/local/bin/docker-compose -f [harbor dir]/docker-compose.yml down      ##中括号部分修改成harbor的文件夹

[Install]
WantedBy=multi-user.target

6.登录Harbor并添加账户
安装成功后,在浏览器地址栏输入http://10.1.1.132:1180打开链接。
输入你设置的用户名和密码
接下来,我们选择用户管理,添加一个管理员账户,为后续打包Docker镜像和上传Docker镜像做准备。
系统管理——用户管理——创建用户

在这里插入图片描述
​ 输入对应信息后点确定
在这里插入图片描述
​ 创建完成后,选中该用户,设置为管理员。
在这里插入图片描述
7.安装后如果要修改端口,如修改为1180
需要修改3个文件
(1)修改harbor.yml文件,在harbor文件夹中

hostname: 10.1.1.1
http:
  port: 1180

​ (2)修改docker-compose.yml文件,在harbor文件夹中

ports:
    - 1180:80

​ (3)修改config.yml文件

cd common/config/registry
vim config.yml

​ 修改的配置项如下所示。

realm: http://10.1.1.1:1180/service/token

​ (4)重启Docker

systemctl daemon-reload
systemctl restart docker.service

​ (5)重启Harbor

docker-compose down
./prepare
docker-compose up -d

3.8 docker安装jenkins,在master节点执行(也可以选择物理机安装,本教程使用物理机安装)

1.安装nfs(之前安装过的话,可以省略此步)

使用 nfs 最大的问题就是写权限,可以使用 kubernetes 的 securityContext/runAsUser 指定 jenkins 容器中运行 jenkins 的用户 uid,以此来指定 nfs 目录的权限,让 jenkins 容器可写;也可以不限制,让所有用户都可以写。这里为了简单,就让所有用户可写了。

如果之前已经安装过nfs,则这一步可以省略。找一台主机,安装 nfs,本例中使用10.1.1.132这台机器。

在命令行输入如下命令安装并启动nfs。

yum install nfs-utils -y
systemctl start nfs-server
systemctl enable nfs-server

2.创建nfs共享目录

​ 在主机上创建 /opt/nfs/jenkins-data目录作为nfs的共享目录,如下所示。

​ mkdir -p /opt/nfs/jenkins-data

​ 接下来,编辑/etc/exports文件,如下所示。

​ vim /etc/exports

​ 在/etc/exports文件文件中添加如下一行配置。

​ /opt/nfs/jenkins-data 10.1.0.0/17(rw,all_squash)

​ 这里的 ip 使用 kubernetes node 节点的 ip 范围,后面的 all_squash 选项会将所有访问的用户都映射成 nfsnobody 用户,不管你是什么用户访问,最终都会压缩成 nfsnobody,所以只要将 /opt/nfs/jenkins-data 的属主改为 nfsnobody,那么无论什么用户来访问都具有写权限。

​ 这个选项在很多机器上由于用户 uid 不规范导致启动进程的用户不同,但是同时要对一个共享目录具有写权限时很有效。

​ 接下来,为 /opt/nfs/jenkins-data目录授权,并重新加载nfs,如下所示。

​ chown -R nfsnobody /opt/nfs/jenkins-data/ ##此处用户为nsf的默认匿名用户,如果这里设置不正确,将导致使用k8s安装jenkins的pod无法启动

​ systemctl reload nfs-server

​ 在K8S集群中任意一个节点上使用如下命令进行验证:

​ showmount -e [NFS_IP]

​ 如果能够看到 /opt/nfs/jenkins-data 就表示 ok 了。

3.创建PV

​ Jenkins 其实只要加载对应的目录就可以读取之前的数据,但是由于 deployment 无法定义存储卷,因此我们只能使用 StatefulSet。

​ 首先创建 pv,pv 是给 StatefulSet 使用的,每次 StatefulSet 启动都会通过 volumeClaimTemplates 这个模板去创建 pvc,因此必须得有 pv,才能供 pvc 绑定。

​ 创建jenkins-pv.yaml文件,文件内容如下所示。

​ apiVersion: v1
​ kind: PersistentVolume
​ metadata:
​ name: jenkins
​ spec:
​ nfs:
​ path: /opt/nfs/jenkins-data
​ server: 10.1.1.132 ##修改成你的nfs主机ip
​ accessModes: [“ReadWriteOnce”]
​ capacity:
​ storage: 1Ti ##修改成你分配给jenkins的存储空间大小

​ 执行如下命令创建pv。

​ kubectl apply -f jenkins-pv.yaml

4.创建serviceAccount

​ 创建service account,因为 jenkins 后面需要能够动态创建 slave,因此它必须具备一些权限。

​ 创建jenkins-service-account.yaml文件,文件内容如下所示。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins
rules:
  - 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"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins

上述配置中,创建了一个 RoleBinding 和一个 ServiceAccount,并且将 RoleBinding 的权限绑定到这个用户上。所以,jenkins 容器必须使用这个 ServiceAccount 运行才行,不然 RoleBinding 的权限它将不具备。

​ RoleBinding 的权限很容易就看懂了,因为 jenkins 需要创建和删除 slave,所以才需要上面这些权限。至于 secrets 权限,则是 https 证书。

​ 执行如下命令创建serviceAccount。

kubectl apply -f jenkins-service-account.yaml

5.安装Jenkins

​ 创建jenkins-statefulset.yaml文件,文件内容如下所示。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  selector:
    matchLabels:
      name: jenkins
  serviceName: jenkins
  replicas: 1
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      name: jenkins
      labels:
        name: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins
      containers:
        - name: jenkins
          image: docker.io/jenkins/jenkins:lts
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
            - containerPort: 32100
          resources:
            limits:
              cpu: 4           
              memory: 4Gi
            requests:
              cpu: 4
              memory: 4Gi
          env:
            - name: LIMITS_MEMORY
              valueFrom:
                resourceFieldRef:
                  resource: limits.memory
                  divisor: 1Mi
            - name: JAVA_OPTS
              # value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
              value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
          livenessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
          readinessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
  # pvc 模板,对应之前的 pv
  volumeClaimTemplates:
    - metadata:
        name: jenkins-home
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Ti

​ jenkins 部署时需要注意它的副本数,你的副本数有多少就要有多少个 pv,同样,存储会有多倍消耗。这里我只使用了一个副本,因此前面也只创建了一个 pv。

​ 使用如下命令安装Jenkins。

kubectl apply -f jenkins-statefulset.yaml 

6.创建Service

​ 创建jenkins-service.yaml文件,文件内容如下所示。

apiVersion: v1
kind: Service
metadata:
  name: jenkins
spec:
  # type: LoadBalancer
  selector:
    name: jenkins
  # ensure the client ip is propagated to avoid the invalid crumb issue when using LoadBalancer (k8s >=1.7)
  #externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      nodePort: 31888		##设置成你需要的端口
      targetPort: 8080
      protocol: TCP
    - name: jenkins-agent
      port: 32100
      nodePort: 32100
      targetPort: 32100
      protocol: TCP
  type: NodePort

​ 使用如下命令安装Service。

kubectl apply -f jenkins-service.yaml 

7.安装 ingress

​ jenkins 的 web 界面需要从集群外访问,这里我们选择的是使用 ingress。创建jenkins-ingress.yaml文件,文件内容如下所示。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins
spec:
rules:
    - http:
        paths:
        - path: /
            backend:
            serviceName: jenkins
            servicePort: 31888          ##这里设置成你需要的端口
    host: jenkins.why.com             ##这里设置成你的主机名

这里,需要注意的是host必须配置为域名或者主机名,否则会报错,如下所示。

​ The Ingress “jenkins” is invalid: spec.rules[0].host: Invalid value: “10.1.1.1”: must be a DNS name, not an IP address

使用如下命令安装ingress。

kubectl apply -f jenkins-ingress.yaml 

待pod启动后执行以下命令获取初始登陆密码

kubectl exec -it jenkins-0  -- cat /var/jenkins_home/secrets/initialAdminPassword

8.最后,由于我这里使用的是虚拟机来搭建相关的环境,在本机访问虚拟机映射的jekins.why.com时,需要配置本机的hosts文件,在本机的hosts文件中加入如下配置项。

10.1.1.129 jekins.why.com

接下来,就可以在浏览器中通过链接:http://jekins.why.com:31888 来访问Jekins了。

3.9 物理机安装jenkins(和3.8二选一即可)

安装Jenkins之前需要安装JDK和Maven,详见3.1章节的7、8

1.启用Jenkins库

​ 运行以下命令以下载repo文件并导入GPG密钥:

wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key

2.安装Jenkins

​ 执行如下命令安装Jenkis。

yum install jenkins

​ 接下来,修改Jenkins默认端口,如下所示。

vim /etc/sysconfig/jenkins

​ 修改后的两项配置如下所示。

JENKINS_JAVA_CMD="/usr/local/jdk1.8.0_212/bin/java"    ##修改成你的java路径
JENKINS_PORT="18080"                                    ##修改成你需要端口号

3.启动Jenkins

​ 在命令行输入如下命令启动Jenkins。

systemctl start jenkins

​ 配置Jenkins开机自启动。

systemctl enable jenkins

​ 查看Jenkins的运行状态。

systemctl status jenkins

4.如果创建工程时连接gitlab报错,还需要安装git

yum install git

3.10 配置jenkins运行环境

1.登录Jenkins

​ 首次安装后,需要配置Jenkins的运行环境。首先,在浏览器地址栏访问链接,打开Jenkins界面。

​ 根据提示使用如下命令到服务器上找密码值,如下所示。

[root@master]# cat /var/lib/jenkins/secrets/initialAdminPassword
71af861c2ab948a1b6efc9f7dde90776

​ 注意:如果是在k8s中安装,获取密码详见3.8章节的7

​ 将密码71af861c2ab948a1b6efc9f7dde90776复制到文本框,点击继续。会跳转到自定义Jenkins页面。

img

​ 选择安装推荐的插件。等待安装完成,如有失败的可选择忽略或重试。

img

2.安装插件

​ 可以根据需要安装插件,如ssh、maven、kubernetes等。
在这里插入图片描述

3.配置jenkins

(1)配置JDK和Maven

在Global Tool Configuration中配置JDK和Maven,如下所示,打开Global Tool Configuration界面。
在这里插入图片描述

按图中配置完成后点击应用、保存退出(仅限于物理机安装的jenkins)
在这里插入图片描述

(2)配置SSH

进入Jenkins的Configure System界面配置SSH,如下所示。

在这里插入图片描述
找到 SSH remote hosts 进行配置。
在这里插入图片描述

配置远程服务器登陆凭据时,可以选择用户名密码,也可以选择非对称加密的公私钥对,配置完成后,点击check connection查看连接结果。

至此,Jenkins的基本配置就完成了。

4.Jenkins发布打包成Docker的spring boot项目到K8s集群

4.1 拉取java8镜像

在任意一台服务器上拉取java8镜像,然后推送至harbor仓库,一会打包构建docker镜像的时候会用到。

docker pull java:8
docker tag java:8 10.1.1.132:1180/library/java:8
docker login 10.1.1.1320:1180
docker push 10.1.1.132:1180/library/java:8

4.2 调整SpringBoot项目的配置

在SpringBoot项目中启动类所在的模块的pom.xml需要引入打包成Docker镜像的配置,在对应的语句块下添加下面的内容:

<properties>
    <!--换成你的harbor仓库地址-->
    <docker.repostory>10.1.1.132:1180</docker.repostory>
    <!--换成你的仓库项目名称-->
    <docker.registry.name>test</docker.registry.name>
    <docker.image.tag>1.0.0</docker.image.tag>
    <docker.maven.plugin.version>1.4.10</docker.maven.plugin.version>
</properties>

<build>
    	<!--换成你想设置的build名称-->
        <finalName>test-starter</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            
            <!-- docker的maven插件,官网:https://github.com/spotify/docker‐maven‐plugin -->
            <!-- Dockerfile maven plugin -->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <version>${docker.maven.plugin.version}</version>
                <executions>
                    <execution>
                    <id>default</id>
                    <goals>
                        <!--如果package时不想用docker打包,就注释掉这个goal-->
                        <goal>build</goal>
                        <goal>push</goal>
                    </goals>
                    </execution>
                </executions>
                <configuration>
                    <contextDirectory>${project.basedir}</contextDirectory>
                    <!-- harbor 仓库用户名及密码-->
             <useMavenSettingsForAuth>useMavenSettingsForAuth>true</useMavenSettingsForAuth>
             <repository>${docker.repostory}/${docker.registry.name}/${project.artifactId}</repository>
                    <tag>${docker.image.tag}</tag>
                    <buildArgs>
                        <!-- 要修改成你的target目录-->
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                </configuration>
            </plugin>

        </plugins>
        
        <resources>
            <!-- 指定 src/main/resources下所有文件及文件夹为资源文件 -->
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.directory}/classes</targetPath>
                <includes>
                    <include>**/*</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

接下来,在SpringBoot启动类所在模块的根目录创建Dockerfile,内容示例如下所示。

#添加依赖环境,前提是将Java8的Docker镜像从官方镜像仓库pull下来,然后上传到自己的Harbor私有仓库中
FROM 10.1.1.132:1180/library/java:8
#指定镜像制作作者,可自己随意设置
MAINTAINER abc   
#运行目录
VOLUME /tmp
#将本地的文件拷贝到容器,一般在项目的target目录下,要根据项目自己修改
ADD target/*jar app.jar
#启动容器后自动执行的命令
ENTRYPOINT [ "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar" ]

在SpringBoot启动类所在模块的根目录创建yaml文件,录入叫做test.yaml文件,其中test-starter替换为你启动pom中的build语句块下设置的名字,内容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-starter
  labels:
    app: test-starter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-starter
  template:
    metadata:
      labels:
        app: test-starter
    spec:
      containers:
      - name: test-starter
        image: 10.1.1.132:1180/test/test-starter:1.0.0
        ports:
        - containerPort: 8088   #修改成你项目的端口

---
apiVersion: v1
kind: Service
metadata:
  name: test-starter
  labels:
    app: test-starter
spec:
  ports:
    - name: http
      port: 8088			#修改成你项目的端口
      nodePort: 30001      ##容器暴露的访问端口,要确定无冲突,否则需修改
  type: NodePort
  selector:
    app: test-starter

4.3 Jenkins配置发布项目

先上传代码到gitlab你的项目上,并按上面的步骤创建好yaml和dockerfile文件,然后创建一个自由风格的jenkins项目,并输入项目名称:
在这里插入图片描述

点击配置进入项目配置页面
在这里插入图片描述
简单介绍几个配置,具体深入的配置可以参考后面给出的参考文档。

丢弃旧的构建,可以节约磁盘空间,这里可以根据项目需求设置。
在这里插入图片描述

这里配置你的gitlab源码访问权限

在这里插入图片描述

这里根据需要配置构建触发的条件

在这里插入图片描述

构建里面选择新增构建,执行shell

在这里插入图片描述

在解析shell代码之前,需要给jenkins配置docker用户组的权限,否则构建时会报错,网上查到的结果无非有三种,

一是通过修改/var/run/docker.sock文件的权限,由660改成666

chmod 666 /var/run/docker.sock

二是将jenkins加入docker用户组

usermod -a -G docker jenkins

三是修改docker的启动文件配置,感兴趣的同学可以自己去尝试,这个方法我没有试过。

这里我用过前两种方法,但第二种似乎并不起作用,构建时仍然会报错。所以暂时用的第一种方式,但这种方式在生产中还是要慎用,毕竟存在一定的安全问题。

shell代码解析

#如果原来本地有同名镜像,则删除本地原有的镜像,不会影响Harbor仓库中的镜像,如果没有,可以在第一次构建时注释掉,否则会报错,仓库的项目名称要根据需要修改
docker rmi 10.1.1.132:1180/test/test-starter:1.0.0
#使用Maven编译、构建Docker镜像,执行完成后本地Docker容器中会重新构建镜像文件
/usr/local/maven-3.6.3/bin/mvn -f ./pom.xml clean install -Dmaven.test.skip=true
#登录 Harbor仓库,根据你创建的harbor用户来设置
docker login 10.1.1.132:1180 -u test -p 123456
#上传镜像到Harbor仓库
docker push 10.1.1.132:1180/test/test-starter:1.0.0
#先将项目目录下的yaml文件复制到k8s集群master节点的执行目录
scp test.yaml root@master:/k8s/demo/
#远程登陆到k8s集群的master节点,前提是前面在jenkins中配置了ssh登陆远程服务器,并测试连接成功,这里使用重定向完成远程服务器操作,EOF是重定向结束标志,大小写敏感
ssh -tt root@master << EOF
#如果非第一次运行,先停止并删除K8S集群中运行的pod;否则注释掉这一行,避免构建报错
/usr/bin/kubectl delete -f /k8s/demo/test.yaml
#将Docker镜像重新发布到K8S集群,注意文件的路径要写正确
/usr/bin/kubectl apply -f /k8s/demo/test.yaml
#执行完成后记得退出远程登陆
exit
#并结束重定向操作
EOF

配置完成后,保存退出项目配置。

在项目界面点击立即构建,执行构建。

在这里插入图片描述

​ 点击构建可以进入查看日志

在这里插入图片描述

在这里插入图片描述

最后显示SUCCESS,则表示构建成功。如果失败,则可以根据日志调整。

构建完成后可以在你的master节点查看pod

kubectl get pod --all-namespaces

在这里插入图片描述

以上状态表示pod成功运行。可以通过下面命令查看暴露的端口

kubectl get svc

在这里插入图片描述

30001即是本次构建项目的访问端口,可以通过master节点ip+端口或域名+端口的形式访问

访问结果如下:

在这里插入图片描述

至此,一个spring boot的项目就构建完成并发布至k8s集群中了。当然,实际生产中的项目远比这个demo复杂,构建之前数据库的配置等操作也一样要先进行,否则构建过程可能会报错。

5.遗留问题

1).如何在k8s中安装jenkins并在pod中安装maven?

由于本案例中先采用的k8s安装jenkins,但是发现没有找到如何安装maven的方法,虽然可以强制登陆到pod中安装maven,但这似乎并不是解决问题的方式,后面我会再查询相关解决方式。

2).如何在k8s中安装jenkins并将项目构建部署在这个k8s中?

​ 方式一:同样采用ssh远程执行命令的方式

​ 方式二:通过jenkins中的k8s调度配置,有待研究

3).配置jenkins多节点,master-slave模式

大致看过相关配置文件,其实并不复杂,由于目前我暂时用不到,所以就没有在教程中实现。其实当时采用k8s安装jenkins想的就是k8s的弹性伸缩功能,但由于目前水平有限,暂时还没有研究,后续有待学习提高。

4).maven构建项目时的module顺序问题,有时顺序不正确可能会导致构建失败

之所以配图用的是spring boot官网的demo展示构建结果,是因为我在这次实验中的开源项目因构建顺序问题,无法成功构建。之前同样的项目同样的配置在我笔记本电脑的虚拟机上构建时的module顺序和这次不同,导致了这个问题的出现。虽然网上有说过maven在构建时,会自动根据module的依赖关系选择构建顺序,但因本人非开发,不是很懂,因此,暂时还没有找到问题症结。后面也会继续跟踪该问题,直到了解并解决。

5).k8s安装gitlab的持久化问题

gitlab在k8s中安装存在代码持久化保存的问题,虽然我们可以通过其它方式,定期备份代码,但是一旦pod出现问题,可能会导致代码丢失。虽然目前没有查询文档,但大概的解决思路有几种:

一是通过物理机安装gitlab

二是通过修改gitlab的yaml文件,在创建pod时将卷挂载到物理机磁盘上,如jenkins使用的nfs

三是通过自动备份的方式定期备份pod中的代码至物理机磁盘

由于时间原因,暂时不做展开。

6).webhook问题

在jenkins的构建触发条件中,有一项可以通过gitlab中代码的改动自动触发构建,这个在第一次实验中我使用过,如果配置邮件发送构建状态,则可以极大提高效率,这里暂时就不做展开,在下一个版本中会逐步完善。

6.总结

总体来说,这一整套环境的搭建其实并不复杂,网上也有很多类似教程,但由于本人初次接触,且经验欠缺,还是花了一定的时间,遇到的主要困难集中在以下几个方面:

1).对原理了解的欠缺

整套环境中,无论哪个软件拿出来,都可以单独出几本书,稍微简单一些的jenkins,虽然整体了解并不复杂,但它可以做二次开发。基于对项目的需求,有很强的定制性。并且,这里只是通过构建spring boot项目就遇到了这么多的问题,还没有构建其它语言的项目呢。因此,了解每个软件的原理很重要。

这里尤其要说的是k8s,我知道这个东西也才几个月,通过网上的一些资料,知道它并不是简简单单的容器编排管理工具。它更像是一种思想和趋势,要想深入学习没有项目实践是很困难的,尤其是能够用到k8s的项目一定不是小项目,这也进一步增加了学习的难度。不过万事开头难,虽然目前仅仅走出了一小步,but it’s better than never。

2).解决问题的思路

由于对原理的欠缺,就必然导致解决问题遇到的麻烦。虽然全部自己查找资料解决了,但大多数问题的解决时间堪忧。其实遇到问题基本上还是逃不开看现象、看日志等方面,经验也是这样慢慢积累的。

3).开发经验的重要性

虽然说作为一个运维工程师,你完全可以选择不懂代码,但这不是我的目标。除非你要去做运维开发,否则懂代码不是让你能写出多好的项目,而是你要懂得基本的原理和代码逻辑,能够独立的排除因代码导致安装、配置故障;其次学习编程重要的编程的思想,了解算法和数据结构背后计算机处理数据的逻辑。懂得了这些对于运维工程师的提升虽然不是立竿见影,但绝对会厚积薄发。

4).钻研精神和自我挖坑

技术之路充满艰难险阻,尤其是对于我这样半路出家的人。但既然选择了,就不要跟自己设定下限,而是要不断突破上限。我始终坚信没有解决不了的问题,只是花费的时间和代价问题,我们积累经验的过程也就是减少付出时间和代价的过程。其次在平时实验环境中要勇敢的给自己挖坑,尽量把遇到的问题做展开,如果这样如果那样,我该如何应对,这样才能够快速成长,我想技术之路不只是给智者的,也是给勤劳者的。

7 参考文档

感谢伟大的互联网,为我提供了几乎是一切!

排名不分先后:

安装部署篇

https://ken.io/note/centos7-gitlab-install-tutorial

https://www.jianshu.com/p/400b4516b98e

https://www.cnblogs.com/guolianyu/p/9477028.html

https://juejin.cn/post/6844903943051411469

https://juejin.cn/post/6844904142922743821

配置管理篇

https://www.cnblogs.com/shawhe/p/11313633.html

https://www.cnblogs.com/zhangs1986/p/11102786.html

https://blog.csdn.net/qq_36184009/article/details/71079726

https://www.jianshu.com/p/5f671aca2b5a

https://mingshan.fun/2018/08/18/build-springboot-by-jenkins-to-deploy-into-docker/

https://yuerblog.cc/2019/02/25/flannel-and-calico/

https://www.jianshu.com/p/86d288ea64c4

https://www.linuxidc.com/Linux/2020-05/163161.htm

https://blog.csdn.net/liubingyu12345/article/details/80737412

https://blog.csdn.net/linfen1520/article/details/109045063

https://juejin.cn/post/6844904154133954567#heading-0

https://blog.51cto.com/14143894/2482586

https://www.jianshu.com/p/7444149be6c6

https://www.cnblogs.com/hellxz/p/easyway_jenkins_integration_k8s.html

;