kubernetes 结合gitlab、helm自动化部署实践,也许能给其他人一些帮助。

大纲

对于想要在Gitlab上实施CICD到kubernetes的朋友,可能需要了解一下gitlab、k8s、helm、nginx-ingress相关技术 的概念和知识。

Kubernetes Cluster(已有k8s集群的可以跳过本节)

对于K8s集群的安装部署,本篇文章使用的是GKE,当然你也可以选择其它公有云提供的k8s的产品。例如AWS的EKS, Microsoft的AKS。另外国内的有Alibaba的 容器服务kubernetes版,国内的公有云现在貌似基本都有k8s 服务,包括专业做CDN的某牛云。说明kubernetes早已成为容器编排领域的事实标准。

创建集群
gcloud beta container --project "hi42-top" clusters create "demo-k8s" --zone "us-east1-b" --no-enable-basic-auth --cluster-version "1.12.6-gke.10" --machine-type "g1-small" --image-type "COS" --disk-type "pd-standard" --disk-size "30" --num-nodes "2" --enable-cloud-logging --enable-cloud-monitoring --no-enable-ip-alias --network "hi42-vpc" --subnetwork "hi42-east" --addons HorizontalPodAutoscaling --enable-autoupgrade --enable-autorepair
验证集群部署

查看刚刚创建的 k8s cluster

$ gcloud container clusters list
NAME      LOCATION    MASTER_VERSION  MASTER_IP      MACHINE_TYPE  NODE_VERSION   NUM_NODES  STATUS
demo-k8s  us-east1-b  1.12.6-gke.10   35.196.230.33  g1-small      1.12.6-gke.10  2          RUNNING

安装kubectl command 根据你客户端选择合适的kubectl发行版

使用gcloud自带命令,自动配置本地的kubectl客户端

$ gcloud container clusters get-credentials demo-k8s

Tips: 这一步gcloud客户端会下载kubeconfig,把内容导入并合并到kubeconfig默认文件 ~/.kube/config

确认下当前使用的集群配置是我们刚刚新建的集群

$ kubectl config current-context
gke_hi42-top_us-east1-b_demo-k8s

Notes:默认config文件里可能会有很多集群配置。如果当前显示的不是我们想要的集群,可以使用kubectl config 进行设置,切换到正确的集群配置。

部署个demo程序验证一下

$ kubectl run hello-server --image gcr.io/google-samples/hello-app:1.0 --port 8080

创建一个类型为LoadBalance的Service,目的是将刚刚创建的服务暴露到集群外部,可外部访问。

kubectl apply -f - <<EOF
kind: Service
apiVersion: v1
metadata:
  name: hello-server
  namespace: default
  labels:
    run: hello-server
  annotations:
    cloud.google.com/load-balancer-type: "Internal"
spec:
  selector:
    run: hello-server
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer
EOF

Notes:cloud.google.com/load-balancer-type: “Internal” 表示内网LB

Tips:在k8s集群内部暴露服务到外部,我们有多种选择:Nodeport、LoadBalancer、Ingress。

查看刚刚创建的Service 资源

$ kubectl get svc
NAME           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
hello-server   LoadBalancer   10.19.254.53   10.11.0.18    80:30105/TCP   7m
kubernetes     ClusterIP      10.19.240.1    <none>        443/TCP        38m

名为hello-server 的 svc已经就绪,外部IP是10.11.0.8,下面使用curl访问该服务。

$ curl 10.11.0.18
Hello, world!
Version: 1.0.0
Hostname: hello-server-5cdf4854df-lf76k

Notes: 我的网络已经和gcp 建立了隧道,所以两地内网是互通的,可以直接访问VPC内部IP。当然没有 条件的话,你也可以选择ssh登陆gcp上的k8s Node节点上,直接访问该IP。

Gitlab CE Setup (1)

Gitlab这一节只介绍一些安装配置方法,因为gitlab配置项目涉及的细节有点多。

Gitlab 安装

见Gitlab-安装文档:https://about.gitlab.com/install/#centos-7

Gitlab 常规配置
Gitlab runner 配置

Gitlab Runner 是根据.gitlab-ci.yaml文件定义的job来跑pipeline的。并且根据注册的runner的执行者不同, 可选择在shell、docker、kubernetes等环境中运行job指令内容。 见:runner executor

参考官方runner安装文档

添加shell runner(当然也可以选择docker executor)

参考runner注册文档

Warning:另外我们需要在该runner上安装Docker-ce

配置Runner使用kubectl & helm

*Warning: 这里我直接使用runner所在主机以shell executor跑pipeline,所以这里直接将kubeconfig 放到了系统上。完整文件位置在gitlab-runner用户的家目录:/home/gitlab-runner/.kube/config *

获取kubeconfig文件: >参考kubernetes cheatsheet

$ kubectl config view --flatten=true > /tmp/demo-k8s.kubeconfig

将该文件demo-k8s.kubeconfig拷贝到runner家目录 /home/gitlab-runner/.kube/config

Notes: 注意config是文件名,不是目录

验证kubectl与集群连接是否正常:

[email protected]:~$ kubectl cluster-info
Kubernetes master is running at https://35.196.230.33
Heapster is running at https://35.196.230.33/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://35.196.230.33/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://35.196.230.33/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
[email protected]:~$ kubectl get po
NAME                            READY   STATUS    RESTARTS   AGE
hello-server-5cdf4854df-lf76k   1/1     Running   0          130m

下一步便是安装在runner主机上安装helm。

Helm Setup

给Helm 来个简短的介绍? Helm是kubernetes里面的包管理工具。把它想象成Linux中的yum、apt。它将 一个服务部署所需要的k8s资源(deployment、secret、configmap、service、ingress…)都打包在一个 Chart中,另外Chart可单独存储在本地。也可以存储到远程git仓库中,或搭建个WebServer可供http下载chart 就行。 Helm 分为client端 和 server端,Server 端(Tiller)部署在k8s集群中,根据helm客户端提交 的chart解析成相应k8s资源的yaml文件,在k8s上创建对应类型的资源。

安装helm

Warning:helm client不是安装在本机,而且安装在gitlab-runner所在主机上。

下载 helm客户端

curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash

HELM init with tiller (RBAC)

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
EOF

初始化并安装helm tiller

$ helm init --service-account tiller --history-max 200
...
Happy Helming!

看到以上输出,表示tiller已经正确部署到kube-system下。有兴趣的可以查看下pod在kube-system namespace下。

Ingress-controller setup

安装ingress controller
helm install --name=nginx-ingress-intranet --namespace=ingress-controller \
--set controller.ingressClass=nginx-ingress-intranet \
--set controller.service.annotations."cloud\.google\.com/load-balancer-type"=Internal \
--set rbac.create=true \
stable/nginx-ingress
验证 ingress controller
kubectl get svc -n ingress-controller
NAME                                     TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-intranet-controller        LoadBalancer   10.19.246.51    10.11.0.19    80:31604/TCP,443:30083/TCP   39m
nginx-ingress-intranet-default-backend   ClusterIP      10.19.253.220   <none>        80/TCP                       39m

从上面可以看出已经成功创建lb,注意这里的外部IP是VPC内网IP。我们curl一下:

$ curl 10.11.0.19
default backend - 404

由于集群里面现在还没有Ingress资源被创建。所以默认将请求转发到default-backend这个服务,当然, 你也可以根据自己需要修改default-backend服务镜像,定制404页面。

创建 SSL Secret

如果你有证书的话,可以以这种方式创建Secret资源,供后面的Ingress资源使用。

$ kubectl create secret tls demo-hi42-top --key demo.hi42.top.key --cert fullchain.cer
secret "demo-hi42-top" created

DNS 配置

配置DNS泛域名解析

上一节我们创建了Ingress controller,现在需要将某域名系统下的所有服务配置指向该IP

*.demo.hi42.top  A 10.11.0.19

Docker Registry

Docker hub (Optional private docker reg)

这里选择了docker hub,省去了搭建私有仓库的步骤。当然也可以选择gcp的 container registry。 需要提供2个变量给后面gitlab-ci使用,docker login 用户名和密码。

Demo Service

Demo code

用go写了简单的web server。两个二哈,两个猫咪。 代码:见 minions

Helm Charts 准备

创建 monions chart
$ mkdir charts && cd charts/ && helm create minions

当前项目根目录charts/minions,简单起见,我们只对文件内容做了一点点修改(包含修改readiness url), 另外我们在pipeline里面deploy job时会定义。进行动态 set。

Gitlab CE Setup (1)

Gitlab project settings

Project开启CI/CD,并设置环境变量。添加2变量:

添加gitlab-runner 到docker用户组

由于我们直接使用gitlab runner shell作为executor,所以需要将gitlab-runner用户加到docker 组中。在gitlab-runner 主机上执行。

$ usermod -aG docker gitlab-runner

Gitlab-PIPELINE 示例

.gitlab-ci.yml 完整内容
stages:
  - build
  - test
  - release
  - deploy

variables:
  # CI_DEBUG_TRACE: "true"
  DOCKER_DRIVER: overlay2

  # application domain name
  QA_DOMAIN_NAME: minions.demo.hi42.top
  PROD_DOMAIN_NAME: minions.hi42.top
  CHART_PATH: ./charts/minions
  INGRESS_CLASS_NAME: nginx-ingress-intranet

  CI_REGISTRY: docker.io
  CONTAINER_PROJECT: hanyifeng/minions
  CONTAINER_IMAGE: $CI_REGISTRY/$CONTAINER_PROJECT
  CONTAINER_TAG: $CI_COMMIT_SHORT_SHA
  # variables 好像不支持这样嵌套赋值。。。Ref:https://docs.gitlab.com/ee/ci/variables/where_variables_can_be_used.html#gitlab-runner-internal-variable-expansion-mechanism
  # CONTAINER_BUILT_IMAGE: "$CONTAINER_IMAGE:$CONTAINER_TAG"
  # CONTAINER_RELEASE_IMAGE: "$CONTAINER_IMAGE:latest"

  # Kubernetes config
  STAGE_NAMESPACE: qa
  PROD_NAMESPACE: production
  STAGE_RELEASE_NAME: minions-qa
  PROD_RELEASE_NAME: minions-product

before_script:
  - echo "Hello Minions"

build:
  stage: build
  script:
    - docker login -u ${CI_REGISTRY_USER} -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - echo "Building image..."
    - docker build --pull -t $CONTAINER_IMAGE:$CONTAINER_TAG .
    - echo "Pushing to ${CI_REGISTRY}..."
    - docker push $CONTAINER_IMAGE:$CONTAINER_TAG
  tags:
    - shell


test1:
  stage: test
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - echo "do unit test"
    - docker pull $CONTAINER_IMAGE:$CONTAINER_TAG
  tags:
    - shell

release-image:
  stage: release
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker pull $CONTAINER_IMAGE:$CONTAINER_TAG
    - docker tag $CONTAINER_IMAGE:$CONTAINER_TAG $CONTAINER_IMAGE:latest
    - docker push $CONTAINER_IMAGE:latest
  tags:
    - shell

deploy_qa:
  stage: deploy
  script:
    - helm upgrade --install
      --set image.repository=$CONTAINER_IMAGE
      --set image.tag=$CONTAINER_TAG
      --set image.pullPolicy=Always
      --set ingress.enabled=true
      --set ingress.annotations."kubernetes\.io/ingress\.class"=$INGRESS_CLASS_NAME
      --set ingress.hosts[0]=$QA_DOMAIN_NAME
      --set ingress.enabled=true --set ingress.tls[0].hosts[0]=$QA_DOMAIN_NAME
      --set ingress.tls[0].secretName=demo-hi42-top
      --set service.port="8080"
      --wait
      --namespace=$STAGE_NAMESPACE
      $STAGE_RELEASE_NAME $CHART_PATH
  environment:
    name: staging
    url: $QA_DOMAIN_NAME
  tags:
    - shell

Warning:由于这里加了ssl,所以需要引用证书,我们之前创建的secret是在default namespace里, 所以需要重新在qa环境下创建一个secret证书。

$ kubectl create secret tls demo-hi42-top --key demo.hi42.top.key --cert fullchain.cer -n qa
验证部署状态

可以在gitlab pipeline上,找到最新的构建,并查看部署状态。这里以一个GIF进行演示。

demo-test1-dog

傻二哈测试

可以尝试修改main.go,将dog.gif,改为cat.gif,重新提交代码到git仓库。之后部署成功后,你将看到两只可爱的小猫咪。。。

demo-test1-dog

Done。

总结

跟之前在使用的Jenkins相比,gitlab-ci和代码管理结合的更完美。但是有些地方还是挺坑的,可能是对gitlab-ci 比较陌生。慢慢的熟悉之后再来发表评论。

其实不管是使用jenkins或者gitlab ci,又或者其他工具也好。原理基本上都差不多,就是使用定义方式上有些不同而已。 后面就是根据项目继续完善优化pipeline。如数据库迁移job,pr review等。