kubernetes 结合gitlab、helm自动化部署实践,也许能给其他人一些帮助。
大纲
- 基础准备工作
- Kubernetes cluster
- Helm
- Helm repository(Optional)
- Nginx-Ingress-Conroller
- Docker Registry
- Gitlab-ce Service
- DNS
- Demo Service
- Helm Charts
对于想要在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 常规配置
- https
- 自动备份
- LDAP
- …
Gitlab runner 配置
Gitlab Runner 是根据.gitlab-ci.yaml文件定义的job来跑pipeline的。并且根据注册的runner的执行者不同, 可选择在shell、docker、kubernetes等环境中运行job指令内容。 见:runner executor
- 安装gitlab runner
参考官方runner安装文档
- 注册runner到gitlab server
添加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与集群连接是否正常:
gitlab-runner@gitlab-slave:~$ 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'.
gitlab-runner@gitlab-slave:~$ 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变量:
- CI_REGISTRY_PASSWORD
- CI_REGISTRY_USER
添加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进行演示。
可以尝试修改main.go,将dog.gif,改为cat.gif,重新提交代码到git仓库。之后部署成功后,你将看到两只可爱的小猫咪。。。
Done。
总结
跟之前在使用的Jenkins相比,gitlab-ci和代码管理结合的更完美。但是有些地方还是挺坑的,可能是对gitlab-ci 比较陌生。慢慢的熟悉之后再来发表评论。
其实不管是使用jenkins或者gitlab ci,又或者其他工具也好。原理基本上都差不多,就是使用定义方式上有些不同而已。 后面就是根据项目继续完善优化pipeline。如数据库迁移job,pr review等。