基于Docker、K8S和GitLab的云原生微服务Auto-DevOps
目前,微服务是比较流行且成熟的后端架构。这几年来,K8S、云原生与Service Mesh也比较火热。
去年开始接触到K8S,然后开始学习互联网架构与云原生,前段时间有空在自己的笔记本中搭建开放环境。环境准备如下:
- DNS服务:阿里云域名服务,解析qinzhiqiang.cn的子域名到本地VM IP
- 宿主:Windows 10 专业版 + Hyper-V,CPU四核八线程,内存32G,使用Docker CLI
- VM1:Ubuntu 18.04 Server + Docker,提供Docker daemon、Docker registry服务,内存2G
- VM2:Ubuntu 18.04 Server + Docker,提供GitLab服务,内存8G
- VM3:Ubuntu 18.04 Server + Docker,提供K8S服务,内存16G
1. DNS解析
因后面的Docker register使用HTTPS比非安全HTTP还方便,不需在每个运行环境中修改docker-daemon中的配置,因此,使用域名服务给各个VM分配独立的域名。
虚拟机 | 域名 | IP |
---|---|---|
VM1 | docker.qinzhiqiang.cn, registry.qinzhiqiang.cn | 192.168.80.112 |
VM2 | gitlab.qinzhiqiang.cn | 192.168.80.113 |
VM3 | k8s.qinzhiqiang.cn, *.k8s.qinzhiqiang.cn | 192.168.80.96 |
可以看到一个特殊的通配符域名*.k8s.qinzhiqiang.cn
,因为GitLab Auto-DevOps需要给每个微服务自动分配子域名与配置Ingress完成自动化测试。
如果没有自己的独立域名,需要新建虚拟机VM0提供DNS服务,然后宿主与其他VM均使用VM0提供的DNS服务。该方案参考Bind9,比阿里云DNS服务配置麻烦多了。
2. CA证书准备
使用XCA软件新建CA Root,再给*.qinzhiqiang.cn
颁发HTTPS服务证书。 最后,在宿主与VM1/2/3中安装CA root.
Ubuntu中导入根证书如下:
sudo cp ksqzq_CA.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo service docker restart
Windows 10 导入证书,可自行百度搜索。
3. 在Ubuntu中安装Docker
3个VM均需要安装Docker,后续所有的服务软件都在Docker中运行,称为“Everything in Docker
”。
安装部署,参考“Ubuntu 18.04中安装Docker”。
4. VM1中开放Docker daemon TCP 2375端口与安装Docker Registry
在纯粹的GitLab Auto DevOps中,因GitLab本身也提供registry服务,该步骤中的“安装Docker registry”是不需要。但为了我们不使用Auto DevOps流水线时,也可手写CI/CD脚本部署微服务,需要一个位置统一存储Docker Images。
开放2375端口,参考“开放Docker Daemon远程访问端口(TCP 2375)”
安装Docker Registry,参考“Ubuntu中部署Docker Registry与Docker Registry UI”。
在K8s集群中安装时:将Docker Registry作为StatefullSets单副本安装到K8s中,将Docker Registry UI作为Deployments单或多副本安装到K8s中,使用Nignx Ingress接入外部流量。
5. Windows 10 中安装Docker Desktop,仅使用Docker CLI
下载 Docker Desktop 并安装,关闭自动运行。添加以下环境变量,仅使用Docker CLI:
变量名 | 变量值 |
---|---|
DOCKER_API_VERSION | 1.39 |
DOCKER_DRIVER | none |
DOCKER_HOST | tcp://docker.qinzhiqiang.cn:2375 |
仅为本地开放环境,没有使用mTLS通信。一个开发环境,Docker本身就是C/S架构,作为本地开发环境,我们只需要一个 Docker daemon就够用了,节省内存资源。
C:\Users\your-user-name>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f378a2fdd041 nginx:alpine "nginx -g 'daemon of…" 5 weeks ago Up 24 hours 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp nginx
78ae84157ec2 joxit/docker-registry-ui:static "/bin/sh -c entrypoi…" 5 weeks ago Up 24 hours 80/tcp registry-ui
df0b6293ce9d registry:2 "/entrypoint.sh /etc…" 3 months ago Up 24 hours 5000/tcp registry
6. 安装K8S单机实例
参考“Ubuntu中部署Kubernetes (K8s)集群”文章,再仔细阅读其中的“6. 部署K8s单例”。
7. 安装 GitLab 服务 (Everything in Docker)
GitLab也使用Docker容器提供服务,同时需要把 SSH 22端口、HTTP 80端口、HTTPS 443端口引入到GitLab容器中。 先修改 VM2 的SSH端口号到2222:
sudo nano /etc/ssh/sshd_config
Port 2222
sudo reboot
再准备数据目录与HTTPS密钥对:
# make dir
sudo mkdir -p /srv/gitlab/config/ssl
sudo mkdir -p /srv/gitlab/data
sudo mkdir -p /srv/gitlab/logs
# Copy SSL key to config dir
***@gitlab-ce:/srv/gitlab/config/ssl$ ls
gitlab.qinzhiqiang.cn.crt
gitlab.qinzhiqiang.cn.key
最后,启动服务:
sudo docker run --detach \
--hostname gitlab.qinzhiqiang.cn \
--env GITLAB_OMNIBUS_CONFIG="external_url 'https://gitlab.qinzhiqiang.cn/'; registry_external_url 'https://gitlab.qinzhiqiang.cn:4567'; gitlab_rails['lfs_enabled'] = true; nginx['redirect_http_to_https'] = true; registry_nginx['redirect_http_to_https'] = true; mattermost_nginx['redirect_http_to_https'] = true;" \
--publish 4567:4567 --publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
等待启动完成,就可以登录 https://gitlab.qinzhiqiang.cn 了。
在K8s集群中安装时:请参考 https://docs.gitlab.com/charts/#installing-gitlab-using-the-helm-chart
8. GitLab 添加 K8S Cluster
先需要把 ksqzq_CA.crt 添加到 GitLab-Runner镜像中,避免Runner无法访问 GitLab 与 GitLab-registry(自动带的Docker镜像服务)。
定制GitLab-Runner镜像的Dockerfile
如下,详情见 https://github.com/kinsprite/custom-auto-devops/tree/master/gitlab-runner ,后续仍需定制 auto-build-image 和 auto-deploy-image:
FROM gitlab/gitlab-runner:alpine-v12.1.0
RUN apk add --no-cache wget ca-certificates
#COPY ksqzq_CA.crt /usr/local/share/ca-certificates/
RUN wget -O /usr/local/share/ca-certificates/ksqzq_CA.crt http://192.168.3.3:8080/ksqzq_CA.crt
RUN update-ca-certificates
在K8S的VM3中准备GitLab-Runner镜像,执行以下命令:
## or use custom image in K8S
docker pull registry.qinzhiqiang.cn/custom-gitlab-runner:v12.0.0
docker tag registry.qinzhiqiang.cn/custom-gitlab-runner:v12.0.0 gitlab/gitlab-runner:alpine-v12.0.0
# then use kubectl to delete the runner POD
添加K8S集群:
- 使用GitLab的root用户登录,添加已有的K8S集群,参考:https://docs.gitlab.com/ee/user/project/clusters/index.html#add-existing-kubernetes-cluster 。其中的Base domain填写为
k8s.qinzhiqiang.cn
。 - 安装GitLab Apps,参考:https://docs.gitlab.com/ee/user/project/clusters/index.html#installing-applications 。安装 Helm Tiller、Ingress、Prometheus、GitLab Runner。
如果要在K8S中玩Serverless,需要安装Istio与Knative,至少需要64G的内存。而32G的内存,只能玩普通的K8S + Auto DevOps。
已安装的GitLab Apps如下:
xxx@k8s-single:~$ kubectl get pods -n gitlab-managed-apps
NAME READY STATUS RESTARTS AGE
ingress-nginx-ingress-controller-7d44688bf-fgk5t 1/1 Running 5 38d
ingress-nginx-ingress-default-backend-66645696bf-kl24n 1/1 Running 5 38d
prometheus-kube-state-metrics-744949b679-5bx42 1/1 Running 9 39d
prometheus-prometheus-server-646888949c-zw8lb 2/2 Running 16 39d
runner-gitlab-runner-7b7ff54f7-pxvvt 1/1 Running 4 38d
tiller-deploy-5c68968c89-g579d 1/1 Running 4 38d
转发VM3的80/443端口到Ingress的NodePort:
方法一:使用iptables转发
xxx@k8s-single:~$ kubectl get svc -n gitlab-managed-apps ingress-nginx-ingress-controller
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-ingress-controller LoadBalancer 10.100.16.253 <pending> 80:30209/TCP,443:31918/TCP 38d
#!/bin/bash
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 30209
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 31918
方法二:使用kubectl port-forward (没有放到后台运行,Ctrl+C 可以中断运行)
sudo kubectl port-forward --address 192.168.80.96 -n gitlab-managed-apps service/ingress-nginx-ingress-controller 80:80
未使用iptables持久化,重新启动 VM3 之后,需要重新执行端口转发命令。
在Windows中验证端口转发是否成功:
C:\Users\your-user-name>curl -v http://unknown.k8s.qinzhiqiang.cn
* Rebuilt URL to: http://unknown.k8s.qinzhiqiang.cn/
* Trying 192.168.80.96...
* TCP_NODELAY set
* Connected to unknown.k8s.qinzhiqiang.cn (192.168.80.96) port 80 (#0)
> GET / HTTP/1.1
> Host: unknown.k8s.qinzhiqiang.cn
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Server: nginx/1.13.8
< Date: Tue, 08 Oct 2019 02:07:15 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 21
< Connection: keep-alive
< Strict-Transport-Security: max-age=15724800; includeSubDomains;
<
default backend - 404* Connection #0 to host unknown.k8s.qinzhiqiang.cn left intact
default backend
返回404
说明配置成功了。
9. GitLab中创建Auto-DevOps项目
- 创建的项目之后,与普通的项目差别在于Settings->CI/CD->Auto DevOps中需要勾选
Default to Auto DevOps pipeline
。 - 使用GoLang创建微服务,只能使用唯一的
5000
端口(目前GitLab只支持5000端口,另外实现Serverless时也只能使用一个端口)。具体代码参考:https://github.com/kinsprite/golang-auto-devops
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
func main() {
println("Hello 'golang-auto-devops'")
mainRouter().Run(":5000") // 监听并在 0.0.0.0:5000 上启动服务
}
func mainRouter() *gin.Engine {
engine := gin.New()
engine.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "OK",
})
})
engine.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
return engine
}
# Dockerfile
# build
FROM golang:1.12.9-alpine3.10 as build
# default port of auto devops is 5000
ENV PORT 5000
EXPOSE 5000
RUN mkdir /app
ADD . /app
ENV GOPROXY https://goproxy.io
ENV GIN_MODE release
WORKDIR /app
RUN go mod vendor
RUN go build -mod=vendor -tags=jsoniter -o golang-auto-devops .
# release
FROM alpine:3.10
RUN mkdir /app
COPY --from=build /app/golang-auto-devops /app/golang-auto-devops
WORKDIR /app
CMD ["/app/golang-auto-devops"]
- 定制CI配置文件。因为我们的CA证书是自己颁发的,需要使用使用定制的构建与部署镜像。如下的.gitlab-ci.yml中,使用共享的Docker
tcp://docker.qinzhiqiang.cn:2375
,可以加速构建,但手写CI脚本时需避免镜像名称重复;Helm部署K8S微服务时,需要设置探针的参数,否则微服务不断重启;因为使用比较新的go module,所以不能使用默认的 test 流水线,只能定制新的 test 流水线。
# .gitlab-ci.yml
include:
- template: Auto-DevOps.gitlab-ci.yml
services:
- name: docker:dind
entrypoint: ["env", "-u", "DOCKER_HOST"]
command: ["dockerd-entrypoint.sh"]
variables:
DOCKER_HOST: tcp://docker.qinzhiqiang.cn:2375/
DOCKER_DRIVER: overlay2
# See https://github.com/docker-library/docker/pull/166
DOCKER_TLS_CERTDIR: ""
# Database
POSTGRES_ENABLED: "false"
# use custom test
TEST_DISABLED: "true"
PERFORMANCE_DISABLED: "true"
# healthz
HELM_UPGRADE_EXTRA_ARGS: "--set livenessProbe.path=/healthz --set readinessProbe.path=/healthz"
before_script:
#- cd $CI_PROJECT_DIR
- export GOPROXY=https://goproxy.io
- export GIN_MODE=release
- export CGO_ENABLED=0
- export GO111MODULE=on
stages:
- test
- build
- deploy # dummy stage to follow the template guidelines
- review
- dast
- staging
- canary
- production
- incremental rollout 10%
- incremental rollout 25%
- incremental rollout 50%
- incremental rollout 100%
- performance
- cleanup
.auto-deploy:
image: "kinsprite/gitlab-auto-deploy-image:v0.1.0"
build:
image: "kinsprite/gitlab-auto-build-image:stable"
golang_test:
stage: test
image: golang:1.12.9-alpine3.10
script:
- go mod vendor
- go vet -mod=vendor .
- go test -mod=vendor .
only:
- branches
- tags
- 修改代码,push到master分支,可以看得流水线正常执行了。
下载流水线的归档文档artifacts.zip
,查看其中的environment_url.txt
,可以看得URL内容如下:
http://ksqzq-golang-auto-devops.k8s.qinzhiqiang.cn
在Windows中,验证微服务是否自动部署成功:
C:\Users\your-user-name>curl http://ksqzq-golang-auto-devops.k8s.qinzhiqiang.cn/ping
{"message":"pong"}
更高级的金丝雀发布、性能测试等功能,GitLab-CE版本没有,只有付费的高级许可才拥有这些功能,但其原理也是GitLab CI/CD的Pipeline,我们也可以在后面添加自己的Stage.
10. 非Auto DevOps时,如何使用CI/CD部署到K8S
没有使用Auto DevOps时,自由度比较高,但是所需完成的事情也比较多。完整代码,见:https://github.com/kinsprite/gitlab-hello-world 。
- 准备条件,在K8S Cluster中安装
Tiller
,参考:https://helm.sh/docs/using_helm/#installing-tiller - 添加独立的
GitLab Runner
。使用Docker版本的,参考:https://docs.gitlab.com/runner/install/docker.html 和 https://docs.gitlab.com/runner/register/index.html#docker 。配置Runner时,需要把CA证书、Docker login信息、docker.sock等信息携带到CI job容器中。在VM2上执行以下命令:
GitLab Runner
sudo mkdir -p /srv/gitlab-runner/config sudo cp ~/ksqzq_CA.crt /srv/gitlab-runner/config/ca.crt
sudo docker run -d –name gitlab-runner –restart always
-v /srv/gitlab-runner/config:/etc/gitlab-runner
-v /var/run/docker.sock:/var/run/docker.sock
-v /root/.docker:/root/.docker
gitlab/gitlab-runner:latest
sudo docker exec -it gitlab-runner cp /etc/gitlab-runner/ca.crt /usr/local/share/ca-certificates/ca.crt sudo docker exec -it gitlab-runner update-ca-certificates sudo docker restart gitlab-runner
sudo docker exec -it gitlab-runner gitlab-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com ) https://gitlab.qinzhiqiang.cn …. …. Please enter the gitlab-ci tags for this runner (comma separated): docker …. ….
GitLab Runner settings
xxx@gitlab-ce:~$ sudo cat /srv/gitlab-runner/config/config.toml concurrent = 1 check_interval = 0
[session_server] session_timeout = 1800
[[runners]] name = “ab2be2113dc2” url = “https://gitlab.qinzhiqiang.cn/" token = “BXy6xxxxxxxxxxxxxxxxxx” executor = “docker” [runners.custom_build_dir] [runners.docker] tls_verify = false image = “golang:1.12.7-alpine3.10” privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/etc/gitlab-runner:/etc/gitlab-runner”, “/var/run/docker.sock:/var/run/docker.sock”, “/root/.docker:/root/.docker”, “/cache”] shm_size = 0 [runners.cache] [runners.cache.s3] [runners.cache.gcs] [runners.custom] run_exec = ""
Update the runner’s config
xxx@gitlab-ce:~$ sudo nano /srv/gitlab-runner/config/config.toml xxx@gitlab-ce:~$ sudo docker restart gitlab-runner
3. 到源代码目录,使用`draft create`命令创建`charts/`文件,后续可以使用`Helm`部署到K8S。Draft文档,见 https://draft.sh/ 。
4. 编写CI配置文件。主要关注构建过程的`docker build`、`docker push`和部署过程的`helm init --client-only`、`helm upgrade`。其中的 tags 务必与步骤2的tags一致。
```yaml
# .gitlab-ci.yml
# This file is a template, and might need editing before it works on your project.
image: golang:1.12.7-alpine3.10
variables:
# Please edit to your GitLab project
REPO_NAME: gitlab.qinzhiqiang.cn/ksqzq/gitlab-hello-world
CI_DEBUG_TRACE: "false"
before_script:
- cd $CI_PROJECT_DIR
- export GOPROXY=https://goproxy.io
- export GIN_MODE=release
- export CGO_ENABLED=0
- export GO111MODULE=on
stages:
- test
# - build
- docker_image
- deploy
testing_job:
stage: test
tags: [docker]
script:
- go mod vendor
- go vet -mod=vendor .
- go test -mod=vendor .
compiling_job:
stage: build
tags: [docker]
script:
- go mod vendor
- go build -mod=vendor -tags=jsoniter -o gitlab-hello-world .
#artifacts:
# paths:
# - gitlab-hello-world
building_job:
image: docker:stable
stage: docker_image
tags: [docker]
script:
- docker build -t registry.qinzhiqiang.cn/gitlab-hello-world:$CI_COMMIT_SHA .
- docker push registry.qinzhiqiang.cn/gitlab-hello-world:$CI_COMMIT_SHA
deploying_job:
image: dtzar/helm-kubectl:latest
stage: deploy
tags: [docker]
script:
- mkdir /root/.kube
- cp $KUBE_CONFIG_FILE /root/.kube/config
- helm init --client-only
- helm upgrade gitlab-hello-world charts/gitlab-hello-world --install --set image.repository=registry.qinzhiqiang.cn/gitlab-hello-world --set image.tag=$CI_COMMIT_SHA
- 在GitLab项目 ksqzq/gitlab-hello-world 的 Settings->CI/CD->Variables 中,添加变量 KUBE_CONFIG_FILE,其内容就是K8S部署之后生成的config文件。
完成以上步骤,push代码到master分支,我们可以看得流水线跑的结果成功了。
部署任务详情如下:
11. 总结
GitLab配合Docker、K8S可以非常顺利地实现Auto DevOps。其强大之处,主要在于GitLab Runner可以运行Docker。有了Docker,各自不同的任务都可以完美隔离运行。CI/CD时,我们依赖的工具集主要有:docker、helm。
从头到尾实现Auto DevOps,需要了解以下知识:SSH、DNS域名解析、RSA证书颁发、CA证书安装、XCA软件使用、Nginx配置、Docker、Docker Registry、K8S、GitLab、GitLab Runner、Helm、Draft、Chart、Iptables。
搭建真实的DevOps环境时,我们应该把所有的Docker Registry、GitLab都放到K8s中,这样可以统一遥测日志、指标等信息,原则是一切都上云。
本文的样例代码,只使用GoLang语言。相关源码地址:
- 定制镜像: https://github.com/kinsprite/custom-auto-devops
- Auto DevOps微服务样例: https://github.com/kinsprite/golang-auto-devops
- 定制CI/CD微服务样例:https://github.com/kinsprite/gitlab-hello-world