乐趣区

关于运维:GitLab-Jenkins-ACK-自动化部署方案



本篇文章从实际角度介绍如何联合咱们罕用的 GitLab 与 Jenkins, 通过 K8s 来实现我的项目的自动化部署, 以公司目前正在应用的生产架构图做为此次解说的重点, 如图所示:

本文波及到的工具和技术包含:

  • GitLab:罕用的源代码管理系统;
  • Jenkins(Jenkins Pipeline):罕用的自动化构建、部署工具,Pipeline 以流水线的形式将构建、部署的各个步骤组织起来;
  • docker(dockerfile):容器引擎,所有利用最终都要以 docker 容器运行,dockerfile 是 docker 镜像定义文件;
  • Kubernetes:Google 开源的容器编排管理系统。

环境背景:

  • 已应用 GitLab 做源码治理,源码按不同的环境建设不同的分支,如:dev (开发分支)、test(测试分支)、pre(预发分支)、master(生产分支);
  • 已搭建 Jenkins 服务;
  • 已有 docker Registry 服务,用于 docker 镜像的存储(能够基于 docker Registry 或 Harbor 自建,或应用云服务,本文应用阿里云容器镜像服务);
  • 已部署了 K8s 集群。

预期成果:

  • 分环境部署利用,使开发环境、测试环境、预发环境及生产环境隔离开来,其中,开发、测试、预发环境部署在同一个 K8s 集群中,但应用不同的 namespace , 生产环境部署在阿里云,应用 ACK 容器服务;
  • 配置尽可能通用化,只须要通过批改大量配置文件的大量配置属性,就能实现新我的项目的自动化部署配置;
  • 开发、测试及预发环境在 push 代码时能够设置主动触发构建与部署,具体依据理论状况配置,生产环境应用独自 ACK 集群及独自 Jenkins 零碎进行部署;
  • 整体交互流程图如下:

我的项目配置文件

首先咱们要在我的项目的根门路下增加一些必要的配置文件。如图所示

包含:

  • dockerfile 文件,用于构建 docker 镜像文件;
  • Docker_build.sh 文件,用于将 docker 镜像打 Tag 后推送到镜像仓库中;
  • 我的项目 Yaml 文件,此文件为部署我的项目到 K8s 集群的主文件。

dockerfile

在我的项目根目录中增加一个 dockerfile 文件(文件名就是 dockerfile), 定义如何构建 docker 镜像,以 Java 我的项目为例:

# 镜像起源
FROM xxxxxxxxxxxxxxxxxxxxxxxxxx.cr.aliyuncs.com/billion_basic/alpine-java:latest

# 拷贝当前目录的利用到镜像
COPY target/JAR_NAME /application/

# 申明工作目录, 不然找不到依赖包,如果有的话
WORKDIR /application

# 申明动静容器卷
VOLUME /application/logs

# 启动命令
# 设置时区
ENTRYPOINT ["java","-Duser.timezone=Asia/Shanghai","-Djava.security.egd=file:/dev/./urandom"]
CMD ["-jar","-Dspring.profiles.active=SPRING_ENV","-Xms512m","-Xmx1024m","/application/JAR_NAME"]

docker_build.sh

在我的项目根目录下创立一个 deploy 文件夹,此文件夹中寄存各个环境我的项目的配置文件,其中 Docker_build.sh 文件就是专为触发我的项目打包为镜像文件、从新打 Tag 后推送到镜像仓库中存在的,同样以 Java 我的项目为例:

# !/bin/bash

# 模块名称
PROJECT_NAME=$1

# 名称空间目录
WORKSPACE="/home/jenkins/workspace"

# 模块目录
PROJECT_PATH=$WORKSPACE/pro_$PROJECT_NAME

# jar 包目录
JAR_PATH=$PROJECT_PATH/target

# jar 包名称
JAR_NAME=$PROJECT_NAME.jar

# dockerfile 目录
dockerFILE_PATH="/$PROJECT_PATH/dockerfile"

# sed -i "s/VAR_CONTAINER_PORT1/$PROJECT_PORT/g" $PROJECT_PATH/dockerfile
sed -i "s/JAR_NAME/$JAR_NAME/g" $PROJECT_PATH/dockerfile
sed -i "s/SPRING_ENV/k8s/g" $PROJECT_PATH/dockerfile

cd $PROJECT_PATH

# 登录阿里云仓库
docker login  xxxxxxxxxxxxxxxxxxxxxxxxxx.cr.aliyuncs.com -u 百瓶网 -p xxxxxxxxxxxxxxxxxxxxxxxxxx

# 构建模块镜像
docker build -t $PROJECT_NAME  . 
docker tag $PROJECT_NAME xxxxxxxxxxxxxxxxxxxxxxxxxx.cr.aliyuncs.com/billion_pro/pro_$PROJECT_NAME:$BUILD_NUMBER

# 推送到阿里云仓库
docker push xxxxxxxxxxxxxxxxxxxxxxxxxx.cr.aliyuncs.com/billion_pro/pro_$PROJECT_NAME:$BUILD_NUMBER

project.yaml文件

project.yaml 定义了我的项目部署到 K8s 集群中所需的项目名称、PV、PVC、namespace、正本数、镜像地址、服务端口、醒目自检、我的项目资源申请配置、文件挂载及 service 等:

# ------------------- PersistentVolume(定义 PV)------------------- #
apiVersion: v1
kind: PersistentVolume
metadata:
# 项目名称
  name: pv-billionbottle-wx
  namespace: billion-pro
  labels:  
    alicloud-pvname: pv-billionbottle-wx
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  csi:
    driver: nasplugin.csi.alibabacloud.com
    volumeHandle: pv-billionbottle-wx
    volumeAttributes:
      server: "xxxxxxxxxxxxx.nas.aliyuncs.com"
      path: "/k8s/java"
  mountOptions:
  - nolock,tcp,noresvport
  - vers=3

---
# ------------------- PersistentVolumeClaim(定义 PVC)------------------- #
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-billionbottle-wx
  namespace: billion-pro
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  selector:
    matchLabels:
      alicloud-pvname: pv-billionbottle-wx      

---      
# ------------------- Deployment ------------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: billionbottle-wx
  name: billionbottle-wx
# 定义 namespace  
  namespace: billion-pro
spec:
# 定义正本数
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: billionbottle-wx
  template:
    metadata:
      labels:
        k8s-app: billionbottle-wx
    spec:
      serviceAccountName: default
      imagePullSecrets:
        - name: registrykey-k8s
      containers:
      - name: billionbottle-wx
# 定义镜像地址  
        image: $IMAGE_NAME 
        imagePullPolicy: IfNotPresent
# 定义自检     
        livenessProbe:
          failureThreshold: 3
          initialDelaySeconds: 60
          periodSeconds: 60
          successThreshold: 1
          tcpSocket:
            port: 8020
          timeoutSeconds: 1
        ports:
# 定义服务端口    
          - containerPort: 8020
            protocol: TCP
        readinessProbe:
          failureThreshold: 3
          initialDelaySeconds: 60
          periodSeconds: 60
          successThreshold: 1
          tcpSocket:
            port: 8020
          timeoutSeconds: 1
# 定义我的项目资源配置     
        resources:
          requests:
            memory: "1024Mi"
            cpu: "300m"
          limits:
            memory: "1024Mi"
            cpu: "300m"
# 定义文件挂载
        volumeMounts:
          - name: pv-billionbottle-key
            mountPath: "/home/billionbottle/key"         
          - name: pvc-billionbottle-wx
            mountPath: "/billionbottle/logs"
      volumes:
        - name: pv-billionbottle-key
          persistentVolumeClaim:
            claimName: pvc-billionbottle-key  
        - name: pvc-billionbottle-wx
          persistentVolumeClaim:
            claimName: pvc-billionbottle-wx

---
# ------------------- Dashboard Service(定义 service)------------------- #
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: billionbottle-wx
  name: billionbottle-wx
  namespace: billion-pro
spec:
  ports:
    - port: 8020
      targetPort: 8020
  type: ClusterIP
  selector:
    k8s-app: billionbottle-wx

这里默认通过 Pipeline 定义了镜像的门路,可间接用变量去替换 $IMAGE_NAME, 且能够在这里间接指定容器的端口而不必去改 dockerfile 文件模版(让模版文件在各个环境复用,通常不须要去做批改),同时增加了 ENV 的配置,能够间接读取 configmap 的配置文件。将 Service type 从默认的 NodePort 改为 ClusterIp 保障我的项目之间只在外部通信。部署不同我的项目时只须要批改 docker_build.shProject.yaml 中的环境变量、项目名称及其他大量配置项,根目录下的 dockerfile 文件能够复用到各个环境。

部署时,咱们要在 K8s 集群中的 Docker 镜像仓库拉取镜像,因而咱们要先在 K8s 中创立镜像仓库拜访凭证(imagePullSecrets)。

# 登录 docker Registry 生成 /root/.docker/config.json 文件
docker login --username=your-username registry.cn-xxxxx.aliyuncs.com
# 创立 namespace billion-pro(我这里时依据我的项目的环境分支名称创立的 namespace)kubectl create namespace billion-pro
# 在 namespace billion-pro 中创立一个 secret 
kubectl create secret registrykey-k8s aliyun-registry-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson --name=billion-pro

Jenkinsfile (Pipeline)

Jenkinsfile 是 Jenkins Pipeline 配置文件, 遵循 Groovy 脚本标准。对于 Java 我的项目的构建部署,Jenkinsfile 的 Pipeline 脚本文件如下:

#!/bin/sh -ilex
def env = "pro"
def registry = "xxxxxxxxxxxxxxx.cn-shenzhen.cr.aliyuncs.com"
def git_address = "http://xxxxxxxxx/billionbottle/billionbottle-wx.git"
def git_auth = "1eb0be9b-ffbd-457c-bcbf-4183d9d9fc35"
def project_name = "billionbottle-wx"
def k8sauth = "8dd4e736-c8a4-45cf-bec0-b30631d36783"
def image_name = "${registry}/billion_pro/pro_${project_name}:${BUILD_NUMBER}"

pipeline{
      environment{BRANCH =  sh(returnStdout: true,script: 'echo $branch').trim()} 
        agent{
            node{label 'master'}
        }
        stages{stage('Git'){
            steps{git branch: '${Branch}', credentialsId: "${git_auth}", url: "${git_address}"
            }
        }
        stage('maven build'){
            steps{sh "mvn clean package -U -DskipTests"}
        }
        stage('docker build'){
            steps{sh "chmod 755 ./deploy/${env}_docker_build.sh && ./deploy/${env}_docker_build.sh ${project_name} ${env}"
            }
        }
        stage('K8s deploy'){
            steps{sh "pwd && sed -i's#\$IMAGE_NAME#${image_name}#'deploy/${env}_${project_name}.yaml"
                kubernetesDeploy configs: "deploy/${env}_${project_name}.yaml", kubeconfigId: "${k8sauth}"
            }
        }
    }
}

Jenkinsfile 的 Pipeline 脚本定义了整个自动化构建部署流程:

  • Code Analyze:能够应用 SonarQube 之类的动态代码剖析工具实现代码查看, 这里先疏忽;
  • Maven Build:启动一个 maven 的程序来实现我的项目 maven 的构建打包, 也能够启动一个 maven 容器, 挂载 maven 本地仓库目录到宿主机, 防止每次都须要从新下载依赖包;
  • docker Build:构建 docker 镜像, 并推送到镜像仓库, 不同环境的镜像通过 tag 前缀辨别,比方开发环境是 dev_,测试环境是 test_,预发环境是 pre_,生产环境是 pro_
  • K8s Deploy:应用 Jenkins 自带插件实现我的项目的部署, 或已有我的项目的更新迭代, 不同环境应用不同的参数配置,K8s 集群的拜访凭证可用 kube_config 来间接配置。

Jenkins 的配置

Jenkins 工作配置

在 Jenkins 中创立一个 Pipeline 的工作, 如图:

配置构建触发器, 将指标分支设置为 master 分支,如图:

配置流水线, 抉择「Pipeline script」并配置 Pipeline 脚本文件, 配置我的项目 Git 地址, 拉取源码凭证等, 如图:

上图中援用的密钥凭据须要提前在 jenkins 中配置,如下图:

保留即实现了我的项目生产环境的 Jenkins 配置, 其它环境同理, 须要留神的是辨别各个环境所对应的分支

Kubernetes 集群性能介绍

K8s 它是基于容器的集群编排引擎, 具备扩张集群、滚动降级回滚、弹性伸缩、主动治愈、服务发现等多种个性能力, 联合目前生产环境的理论状况, 重点介绍几个罕用的性能点, 如需具体理解其它性能, 请间接在 Kubernets 官网 查问。

Kubernetes 架构图

从宏观上来说 Kubernetes 的整体架构, 蕴含 Master、Node 以及 Etcd。

Master 即主节点, 负责管制整个 kubernetes 集群。它蕴含 Api Server、Scheduler、Controller 等局部, 它们都须要和 Etcd 进行交互以存储数据。

  • Api Server:次要提供资源操作的对立入口, 这样就屏蔽了与 Etcd 的间接交互, 性能蕴含平安、注册与发现等;
  • Scheduller:负责依照肯定的调度规定将 pod 调度到 Node 上;
  • Controller:资源控制中心, 确保资源处于预期的状态。

Node 即工作节点,为整个集群提供计算力, 是容器真正运行的中央, 包含运行容器、kubelet、kube-proxy。

  • kubelet:次要工作是治理容器的生命周期, 联合 cAdvisor 进行监控、健康检查以及定期上报节点状态;
  • kube-proxy:次要利用 service 提供集群外部的服务发现和负载平衡, 同时监听 service/endpoints 变动刷新负载平衡。

容器编排

Kubernetes 中有诸多编排相干的管制资源, 例如编排无状态利用的 deployment, 编排有状态利用的 statefulset, 编排守护过程 daemonset 以及编排离线工作的 job/cronjob 等等。

咱们以目前生产环境利用的 deployment 为例, deployment、replicatset、pod 之间的关系是一种层层管制的关系, 简略来说,replicatset 管制 pod 的数量, 而 deployment 管制 replicatset 的版本属性, 这种设计模式也为两种最根本的编排动作实现了根底, 即数量管制的程度扩缩容, 版本属性管制的更新 / 回滚。

程度扩缩容

程度扩缩容十分好了解, 咱们只须要批改 replicatset 管制的 pod 正本数量即可, 比方从 2 改到 3 , 那么就实现了程度扩容这个动作, 反之即程度膨胀。

滚动更新部署(Rolling Update)

滚动部署是 K8s 中的默认部署策略, 它用新版本的 pod 一个一个地替换应用程序先前版本的 pod , 而没有任何集群停机的工夫, 滚动部署迟缓地用新版本应用程序的实例替换之前版本的应用程序实例, 如图所示:

在滚动更新的理论利用中咱们能够配置 RollingUpdateStrategy 来管制滚动更新策略, 另外还有两个选项能够让咱们微调更新过程:

  • maxSurge:更新期间能够创立 pod 数量超过所需 pod 的数量, 这能够是正本计数的相对数量或百分比, 默认值为 25%;
  • maxUnavailable:更新过程中不可用的 pod 数量, 这能够是正本计数的相对数量或百分比, 默认值为 25%。

微服务(service)

理解微服务前, 咱们要线理解一个很重要的资源对象 —— service

在微服务中, pod 能够对应实例, 那么 service 对应的就是微服务, 而在服务调用的过程中,service 的呈现解决了两个问题:

  • pod 的 ip 不是固定的, 利用非固定的 ip 进行网络调用不事实;
  • 服务调用须要对不同的 pod 进行负载平衡。

service 通过 label 选择器选取适合的 pod, 构建出一个 endpoints, 即 pod 负载平衡列表, 理论使用中, 个别咱们会为同一个微服务的 pod 实例都搭上相似 app=xxx 的标签, 同时为该微服务创立一个标签选择器为 app=xxx 的 service。

Kubernetes 中的网络

K8s 的网络通讯, 首先得有“三通”根底:

  • node 到 pod 之间能够互通;
  • node 的 pod 之间能够互通;
  • 不同 node 之间的 pod 能够互通。

简略来说不同 pod 之间通过 cni0/docker0 网桥实现了通信,node 拜访 pod 也是通过网桥通信,
而不同的 node 之间的 pod 通信有很多种实现形式, 包含当初比拟广泛的 flannel 的 vxlan/hostgw 模式等,flannel 通过 etcd 获知其它 node 的网络信息, 并会为本 node 创立路由表, 最终使得不同 node 间能够实现跨主机通信。

小结

到当初为止, 已根本介绍分明了咱们生产环境整体架构中应用到的根底组件的相干概念,它们是如何运行的, 以及微服务是怎么运行在 Kubernetes 中的, 但波及到配置核心、监控及告警等一些其它的组件暂未具体介绍, 争取尽快更新这部分内容。

更多精彩请关注咱们的公众号「百瓶技术」,有不定期福利呦!

退出移动版