传统的应用开发发布模式

  • 开发团队 在开发环境中完成软件开发,单元测试,测试通过,提交到代码版本管理库。
  • 运维团队 把应用部署到测试环境,供QA团队测试,测试通过后部署生产环境。
  • QA 团队 purple 进行测试,测试通过后通知部署人员发布到生产环境。

SVN

传统发布面临的挑战

  • 错误发现不及时 很多错误在项目的早期可能就存在,到最后集成的时候才发现问题。
  • 人工低级错误发生 产品和服务交付中的关键活动全都需要手动操作。
  • 团队工作效率低 需要等待他人的工作完成后才能进行自己的工作。
  • 开发运维对立 开发人员想要快速更新,运维人员追求稳定,各自的针对的方向不同。

经过上述问题我们需要作出改变,如何改变?

持续集成与持续交付

软件开发的连续方法基于自动执行脚本,以最大程度地减少在开发应用程序时引入错误的机会。从开发新代码到部署新代码,他们几乎不需要人工干预,甚至根本不需要干预。

它涉及到在每次小的迭代中就不断地构建,测试和部署代码更改,从而减少了基于错误或失败的先前版本开发新代码的机会。

此方法有三种主要方法,每种方法都将根据最适合您的策略的方式进行应用。

持续集成(Continuous Integration)

  • 持续合并开发人员正在开发编写的所有代码的一种做法。
  • 通常一天内进行多次合并和提交代码。
  • 从存储库或生产环境中进行构建和自动化测试,以确保没有集成问题并及早发现任何问题。

开发人员提交代码的时候一般先在本地测试验证,只要开发人员提交代码到版本控制系统就会触发一条提交流水线,对本次提交进行验证。

持续交付(Continuous Delivery)

持续交付是超越持续集成的一步。不仅会在推送到代码库的每次代码更改时都进行构建和测试,而且,作为附加步骤,即使部署是手动触发的,它也可以连续部署。此方法可确保自动检查代码,但需要人工干预才能从策略上手动触发更改的部署。

持续部署(Continuous Deployment)

  • 通常可以通过将更改自动推送到发布系统来随时将软件发布到生产环境中。
  • 持续部署会更进一步,并自动将更改推送到生产中。

类似于持续交付,持续部署也是超越持续集成的又一步。不同之处在于,您无需将其手动部署,而是将其设置为自动部署。部署您的应用程序完全不需要人工干预。

CI/CD的价值体现

  • 尽早反馈,尽早发现错误。
  • 减少集成问题,每次发现问题当时解决,避免问题堆积。
  • 每次更改都能成功发布,降低发布风险。
  • 更加频繁的交付价值,客户反馈。

推荐常用的CI/CD工具

Jenkins

专业的CI工具,可扩展自动化服务器、安装配置简单、丰富的插件库、分布式架构设计、支持所有的平台、可视化的管理页面。
Jenkins

GitLab

端到端DevOps工具,常用功能:代码审查、问题跟踪、动态订阅、易于扩展、项目wiki、多角色项目管理、项目代码在线编译预览、CI工具集成。
GitLab

GitLab CI/CD组件

  • GitLab CI/CD
    • GitLab的一部分,GitLab是一个Web应用程序,具有将其状态存储在数据库中的API。
    • 除了GitLab的所有功能之外,它还管理项目/构建并提供一个不错的用户界面。
  • GitLab Runner
    • 是一个处理构建的应用程序。
    • 可以单独部署,并通过API与GitLab CI / CD一起使用。

GitLab CI/CD

  • .gitlab-ci.yml 定义流水线作业运行,位于应用项目根目录下。

GitLab CI/CD工作原理

  • 将代码托管到Git存储库。
  • 在项目根目录创建ci文件 .gitlab-ci.yml ,在文件中指定构建,测试和部署脚本。
  • GitLab将检测到它并使用名为GitLab Runner的工具运行脚本。
  • 脚本被分组为作业,它们共同组成了一个管道。

安装GitLabRunner

为什么不是 GitLab CI 来运行那些构建任务?
一般来说,构建任务都会占用很多的系统资源 (譬如编译代码),而 GitLab CI 又是 GitLab 的一部分,如果由 GitLab CI 来运行构建任务的话,在执行构建任务的时候,GitLab 的性能会大幅下降。

GitLab CI 最大的作用是管理各个项目的构建状态,因此,运行构建任务这种浪费资源的事情就交给 GitLab Runner 来做拉!
因为 GitLab Runner 可以安装到不同的机器上,所以在构建任务运行期间并不会影响到 GitLab 的性能

在GNU / Linux上手动安装GitLab Runner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如果您不能使用deb/rpm存储库安装GitLab Runner,或者您的GNU / Linux操作系统不在支持的版本中,则可以使用以下一种方法手动安装它,这是最后的选择。

通过deb或rpm软件包
下载软件包

在https://gitlab-runner-downloads.s3.amazonaws.com/latest/index.html上找到最新的文件名和选项 。
选择一个版本并下载二进制文件,如文档所述,该文件用于下载任何其他标记的 GitLab Runner发行版。
例如,对于Debian或Ubuntu:

curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_<arch>.deb

dpkg -i gitlab-runner_<arch>.deb

dpkg -i gitlab-runner_<arch>.deb
例如,对于CentOS或Red Hat Enterprise Linux:

curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_<arch>.rpm

rpm -i gitlab-runner_<arch>.rpm

rpm -Uvh gitlab-runner_<arch>.rpm

进入GitLab 管理中心 -> 概述 -> Runners页面,获取Gitlab 注册令牌
GitLabRunner

在gitlab-runner服务器执行命令注册runner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@gitlab ~]# gitlab-runner register
Runtime platform arch=amd64 os=linux pid=22990 revision=e0218c92 version=14.3.2
Running in system-mode.

Enter the GitLab instance URL (for example, https://gitlab.com/):
http://192.168.1.55/
Enter the registration token:
zNSMJ61xg95zVZxKEp5A
Enter a description for the runner:
[gitlab]: runner001
Enter tags for the runner (comma-separated):
runner001
Registering runner... succeeded runner=zNSMJ61x
Enter an executor: parallels, ssh, docker+machine, docker-ssh+machine, kubernetes, custom, docker, docker-ssh, shell, virtualbox:
docker
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

runner配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@ops177 ~]# cat /etc/gitlab-runner/config.toml 
concurrent = 20
check_interval = 0

[session_server]
session_timeout = 1800

[[runners]]
name = "ops177"
url = "http://192.168.1.55/"
token = "B_SUUoftkts1LxfzS8hm"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "registry.cn-shanghai.aliyuncs.com/wikifx/kong:busybox"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache","/var/run/docker.sock:/var/run/docker.sock"]
shm_size = 0
# 将本地docker.sock挂载到docker镜像中实现打包出来的业务镜像持久化到本地

刷新页面
GitLabRunner

持续集成相关的概念

Pipeline
一次 Pipeline 其实相当于一次构建任务,里面可以包含多个流程,如安装依赖、运行测试、编译、部署测试服务器、部署生产服务器等流程。
任何提交或者 Merge Request 的合并都可以触发 Pipeline,如下所示:

1
2
3
4
+------------------+           +----------------+
| | trigger | |
| Commit / MR +---------->+ Pipeline |
+------------------+ +----------------+

Stages
Stages 表示构建阶段,说白了就是上面提到的流程。
我们可以在一次 Pipeline 中定义多个 Stages,这些 Stages 会有以下特点:

  • 所有 Stages 会按照顺序运行,即当一个 Stage 完成后,下一个 Stage 才会开始
  • 只有当所有 Stages 完成后,该构建任务 (Pipeline) 才会成功
  • 如果任何一个 Stage 失败,那么后面的 Stages 不会执行,该构建任务 (Pipeline) 失败

因此,Stages 和 Pipeline 的关系就是:

1
2
3
4
5
6
+--------------------------------------------------------+
| Pipeline |
| +-----------+ +------------+ +------------+ |
| | Stage 1 |---->| Stage 2 |----->| Stage 3 | |
| +-----------+ +------------+ +------------+ |
+--------------------------------------------------------+

Jobs
Jobs 表示构建工作,表示某个 Stage 里面执行的工作。
我们可以在 Stages 里面定义多个 Jobs,这些 Jobs 会有以下特点:

  • 相同 Stage 中的 Jobs 会并行执行
  • 相同 Stage 中的 Jobs 都执行成功时,该 Stage 才会成功
  • 如果任何一个 Job 失败,那么该 Stage 失败,即该构建任务 (Pipeline) 失败

所以,Jobs 和 Stage 的关系就是:

1
2
3
4
5
6
+------------------------------------------+
| Stage 1 |
| +---------+ +---------+ +---------+ |
| | Job 1 | | Job 2 | | Job 3 | |
| +---------+ +---------+ +---------+ |
+------------------------------------------+

gitlab-ci.yml

配置好 Runner 之后,我们要做的事情就是在项目根目录中添加 .gitlab-ci.yml 文件。当我们添加了 .gitlab-ci.yml 文件后,每次提交代码或者合并 MR 都会自动运行构建任务。

Pipeline 是通过提交代码或者合并 MR 来触发的。那么 Pipeline 和 .gitlab-ci.yml 有什么关系呢?
其实 .gitlab-ci.yml 就是拿来在定义 Pipeline 的。

使用GitLab自带的流水线,必须要定义流水线的内容,而定义内容的文件默认叫做.gitlab-ci.yml,使用yml的语法进行编写。
目前任务关键词有28个,全局的关键词有10个,两者重叠的有很多。掌握了这些关键词的用法,可以编写逻辑严谨,易于扩展的流水线。

任务的28个关键词分别是,
script, after_script, allow_failure, artifacts, before_script, cache, coverage, dependencies, environment, except, extends, image, include, interruptible, only, pages, parallel, release, resource_group, retry, rules, services, stage, tags, timeout, trigger, variables, when

全局的关键词
image,services,before_script,after_script,tags,cache,artifacts,retry,timeout,interruptible

最常任务中最常用的是这七个script,artifacts,stage, when,tags,image,cache,
下面我先来详细介绍一下这七个关键词,知道了这个七个关键词,一般的流水线随随便便拿下,

gitlab-ci.yml常用关键词

  • stages 定义管道阶段的名称和顺序。
  • stage 将指定作业(job)分配给指定stage(阶段)
  • script 由运行程序执行的 Shell 脚本。[单行/多行]
  • tags 来选择可用于该项目的runner[project > settings > ci_cd > Runner]
  • retry 使用retry配置作业多少次失败的情况下重试。
  • image 指定一个基础Docker镜像作为基础运行环境,经常用到的镜像有node java python docker
  • only/except 限定当前任务的执行条件,如:只有在指定分区才能被看到
  • when 实现再发生故障或尽管发生故障时仍能运行的作业
    • on_success 所有任务执行成功后
    • on_failure 当至少一个任务失败后
    • always 执行作业,而不考虑作业在早期阶段的状态。
    • manual 手动执行任务
    • delayed 延迟执行任务
    • never
      在rules中不排除执行的任务
      在workflow:rules不允许的流水线
  • cache 是将当前工作环境目录中的一些文件,文件夹等存储起来。用于在各个任务初始化的时候恢复

基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#自定义阶段
stages:
- buildsoft
- builddocker
- deploy_dev
- clear

variables:
GOPROXY: "https://goproxy.cn"
IMAGENAME: "ipinversion"
IMAGE: 192.168.200.177/dev_image/ipinversion:${IMAGENAME}_${CI_PIPELINE_ID}
SERVICE_NAME: ipinversion
NAMESPACE: baseservice
NODE_ENV: development

buildsoft:
stage: buildsoft
image: 192.168.200.177/base/golang:latest
script:
- go build -o ipinversion .
- mkdir app/
- cp ipinversion app/
- cp -R conf app/
- cp -R Dockerfile app/
- mkdir -p app/data
# tags:
# - running01
retry: 2 #重试机制
artifacts:
paths:
- app/

builddocker:
stage: builddocker
image: 192.168.200.177/base/dood:latest
services:
# - name: docker:dind
# command:
# [
# '--insecure-registry=192.168.200.177',
# ]
before_script:
- echo "$IMGHUBUSER"
- echo "$IMGHUBPWD"
- docker login 192.168.200.177 -u "$IMGHUBUSER" -p "$IMGHUBPWD"
script:
- docker build -t ${IMAGE} -f app/Dockerfile app/
- docker push ${IMAGE}
after_script:
- docker logout ${REGISTRY_URL}
dependencies:
- buildsoft
# tags:
# - running01
only:
- master
# when:
# manual

deploy_dev:
stage: deploy_dev
before_script:
- mkdir ~/.kube
- cat ${KUBE_CONFIG} > ~/.kube/config
image: 192.168.200.177/base/kubectl:v1.18.12
script:
- cat ${APPLYFILE} > applyfile.yaml
- envsubst < applyfile.yaml | kubectl apply -f -
# tags:
# - running01
only:
- master

clear:
stage: clear
script:
- rm -rf *
- ls -l
# tags:
# - running01

APPLYFILE发布文件模板变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
apiVersion: apps/v1
kind: Deployment
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: $SERVICE_NAME
template:
metadata:
labels:
app: $SERVICE_NAME
spec:
volumes:
- name: logsmk #volume名字
hostPath:
path: /var/applogs
- name: database #volume名字
persistentVolumeClaim:
claimName: ipinversionpvc
containers:
- name: $SERVICE_NAME
image: ${IMAGE}
imagePullPolicy: IfNotPresent
env:
- name: ENVIRONMENT
valueFrom:
configMapKeyRef:
name: ipinversion
key: ENVIRONMENT
ports:
- containerPort: 8031
resources:
requests:
memory: 100Mi
cpu: 100m
limits:
memory: 1024Mi
cpu: 1000m
livenessProbe:
httpGet:
path: /get/check
port: 8031
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /get/check
port: 8031
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 5
periodSeconds: 15
volumeMounts:
- name: logsmk
mountPath: /var/applogs/
- name: database
mountPath: /app/data/
---
apiVersion: v1
kind: Service
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8031 #pod对外的端口
selector:
app: $SERVICE_NAME #匹配到此label的pod的8002端口,会加入到此k8s lb中
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
spec:
rules:
- host: ipinversion.fxeyeinterface.com
http:
paths:
- path: /
backend:
serviceName: $SERVICE_NAME
servicePort: 80

kubectl镜像Dockerfie

1
2
3
4
5
6
7
8
9
10
11
12
[root@node001 kubectl]# ls
Dockerfile kubectl
[root@node001 kubectl]# cat Dockerfile
FROM registry.cn-shanghai.aliyuncs.com/wikifx/base:alpine-glibc-Shanghai

MAINTAINER felix

ENV TZ "Asia/Shanghai"

COPY kubectl /usr/local/bin/
RUN chmod +x /usr/local/bin/kubectl &&\
apk add gettext

更多语法参照:链接

流水线

gitlab ci/cd中变量的使用

三种变量的使用

  • 在.gitlab-ci.yml中自己定义
  • pipeline中预定义的变量
  • 设置在项目中设置变量

变量类型

  • k-v 变量
  • 文件
  1. 流水线全局变量
1
2
variables:
FELIX: "flask-test"
  1. 设置在项目中设置变量

variables

  1. pipeline预定义变量官方文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
$ export
export CI='true'
export CI_API_V4_URL='http://192.168.1.55/api/v4'
export CI_BUILDS_DIR='/builds'
export CI_BUILD_BEFORE_SHA='0920fb2150639324fc1a3b20639ba7b02f6e1aee'
export CI_BUILD_ID='498'
export CI_BUILD_MANUAL='true'
export CI_BUILD_NAME='deploy'
export CI_BUILD_REF='22ce717a8dff73b3abb24ce0e9773e7f0677dbff'
export CI_BUILD_REF_NAME='master'
export CI_BUILD_REF_SLUG='master'
export CI_BUILD_STAGE='deploy'
export CI_BUILD_TOKEN='[MASKED]'
export CI_COMMIT_AUTHOR='Administrator <admin@example.com>'
export CI_COMMIT_BEFORE_SHA='0920fb2150639324fc1a3b20639ba7b02f6e1aee'
export CI_COMMIT_BRANCH='master'
export CI_COMMIT_DESCRIPTION=''
export CI_COMMIT_MESSAGE='Update .gitlab-ci.yml'
export CI_COMMIT_REF_NAME='master'
export CI_COMMIT_REF_PROTECTED='true'
export CI_COMMIT_REF_SLUG='master'
export CI_COMMIT_SHA='22ce717a8dff73b3abb24ce0e9773e7f0677dbff'
export CI_COMMIT_SHORT_SHA='22ce717a'
export CI_COMMIT_TIMESTAMP='2021-10-19T12:25:07+00:00'
export CI_COMMIT_TITLE='Update .gitlab-ci.yml'
export CI_CONCURRENT_ID='0'
export CI_CONCURRENT_PROJECT_ID='0'
export CI_CONFIG_PATH='.gitlab-ci.yml'
export CI_DEFAULT_BRANCH='master'
export CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX='192.168.1.55:80/public-projects/dependency_proxy/containers'
export CI_DEPENDENCY_PROXY_PASSWORD='[MASKED]'
export CI_DEPENDENCY_PROXY_SERVER='192.168.1.55:80'
export CI_DEPENDENCY_PROXY_USER='gitlab-ci-token'
export CI_DISPOSABLE_ENVIRONMENT='true'
export CI_JOB_ID='498'
export CI_JOB_JWT='[MASKED]'
export CI_JOB_MANUAL='true'
export CI_JOB_NAME='deploy'
export CI_JOB_STAGE='deploy'
export CI_JOB_STARTED_AT='2021-10-19T12:26:08Z'
export CI_JOB_STATUS='running'
export CI_JOB_TOKEN='[MASKED]'
export CI_JOB_URL='http://192.168.1.55/public-projects/back-end/ip_inversion/-/jobs/498'
export CI_NODE_TOTAL='1'
export CI_PAGES_DOMAIN='example.com'
export CI_PAGES_URL='http://public-projects.example.com/back-end/ip_inversion'
export CI_PIPELINE_CREATED_AT='2021-10-19T12:25:08Z'
export CI_PIPELINE_ID='71'
export CI_PIPELINE_IID='19'
export CI_PIPELINE_SOURCE='push'
export CI_PIPELINE_URL='http://192.168.1.55/public-projects/back-end/ip_inversion/-/pipelines/71'
export CI_PROJECT_DIR='/builds/public-projects/back-end/ip_inversion'
export CI_PROJECT_ID='9'
export CI_PROJECT_NAME='ip_inversion'
export CI_PROJECT_NAMESPACE='public-projects/back-end'
export CI_PROJECT_PATH='public-projects/back-end/ip_inversion'
export CI_PROJECT_PATH_SLUG='public-projects-back-end-ip-inversion'
export CI_PROJECT_REPOSITORY_LANGUAGES='go,dockerfile'
export CI_PROJECT_ROOT_NAMESPACE='public-projects'
export CI_PROJECT_TITLE='ip反转接口'
export CI_PROJECT_URL='http://192.168.1.55/public-projects/back-end/ip_inversion'
export CI_PROJECT_VISIBILITY='private'
export CI_REGISTRY_PASSWORD='[MASKED]'
export CI_REGISTRY_USER='gitlab-ci-token'
export CI_REPOSITORY_URL='http://gitlab-ci-token:[MASKED]@192.168.1.55/public-projects/back-end/ip_inversion.git'
export CI_RUNNER_DESCRIPTION='running01'
export CI_RUNNER_EXECUTABLE_ARCH='linux/amd64'
export CI_RUNNER_ID='3'
export CI_RUNNER_REVISION='e0218c92'
export CI_RUNNER_SHORT_TOKEN='_Q7D-qHB'
export CI_RUNNER_TAGS='running01'
export CI_RUNNER_VERSION='14.3.2'
export CI_SERVER='yes'
export CI_SERVER_HOST='192.168.1.55'
export CI_SERVER_NAME='GitLab'
export CI_SERVER_PORT='80'
export CI_SERVER_PROTOCOL='http'
export CI_SERVER_REVISION='7fd059d6f16'
export CI_SERVER_URL='http://192.168.1.55'
export CI_SERVER_VERSION='14.0.4'
export CI_SERVER_VERSION_MAJOR='14'
export CI_SERVER_VERSION_MINOR='0'
export CI_SERVER_VERSION_PATCH='4'
export ENV='/root/.bashrc'
export FELIX='cakepanit.com'
export FF_CMD_DISABLE_DELAYED_ERROR_LEVEL_EXPANSION='false'
export FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR='false'
export FF_ENABLE_BASH_EXIT_CODE_CHECK='false'
export FF_ENABLE_JOB_CLEANUP='false'
export FF_GITLAB_REGISTRY_HELPER_IMAGE='true'
export FF_NETWORK_PER_BUILD='false'
export FF_SCRIPT_SECTIONS='false'
export FF_SKIP_NOOP_BUILD_STAGES='true'
export FF_USE_DIRECT_DOWNLOAD='true'
export FF_USE_DYNAMIC_TRACE_FORCE_SEND_INTERVAL='false'
export FF_USE_FASTZIP='false'
export FF_USE_LEGACY_KUBERNETES_EXECUTION_STRATEGY='false'
export FF_USE_NEW_BASH_EVAL_STRATEGY='false'
export FF_USE_NEW_SHELL_ESCAPE='false'
export FF_USE_POWERSHELL_PATH_RESOLVER='false'
export FF_USE_WINDOWS_LEGACY_PROCESS_STRATEGY='true'
export GITLAB_CI='true'
export GITLAB_FEATURES=''
export GITLAB_USER_EMAIL='admin@example.com'
export GITLAB_USER_ID='1'
export GITLAB_USER_LOGIN='root'
export GITLAB_USER_NAME='Administrator'
export HOME='/root'
export HOSTNAME='runner-q7d-qhb-project-9-concurrent-0'
export KUBE_CONFIG='/builds/public-projects/back-end/ip_inversion.tmp/KUBE_CONFIG'
export NAME='flask-test'
export OLDPWD='/'
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
export PWD='/builds/public-projects/back-end/ip_inversion'
export SHLVL='2'

最终可以实现:

  1. 开发提交代码
  2. 自动触发流水线构建编译程序
  3. 自动打包镜像推入镜像仓库(dood实现)
  4. 自动生成k8s yaml文件并自动发布到k8s集群

upload successful
upload successful

单仓库多项目扩展写法

如果一个git仓库中存在多个项目。每个目录中存在本项目的dockerfile时。我们应该通过needs定义流水线中任务的依赖关系。具体写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# gitlab-ci.yml
stages:
- builddocker
- deploy_dev

variables:
WAREHOUSE: 192.168.200.177
IMAGE: ${WAREHOUSE}/dev_image/${PROJECT}:${CI_PIPELINE_ID}
SERVICE_NAME: ${PROJECT}
NAMESPACE: bit

.deploy_k8s: &deploy_k8s |
cat ${APPLYFILE} > applyfile.yaml
envsubst < applyfile.yaml | kubectl apply -f -

.init_k8s: &init_k8s |
[ -d ~/.kube ] || mkdir ~/.kube
cat ${KUBE_CONFIG} > ~/.kube/config

.relay: &relay |
docker logout ${WAREHOUSE}
echo "PROJECT=${PROJECT}" > build.env
echo "DOMAIN=${DOMAIN}" >> build.env
echo "EXPOSEPORT=${EXPOSEPORT}" >> build.env

.build: &docker |
docker build -t ${IMAGE} -f ${DOCKER_FILE_PATH} ${CONTEXT_PATH}
docker push ${IMAGE}

build-bit_survey_web:
variables:
PROJECT: bit-survey-web
DOMAIN: survey.wikibit.com
EXPOSEPORT: 8088
DOCKER_FILE_PATH: "trunk/Wikibit.Survey.Web/Dockerfile"
CONTEXT_PATH: "trunk/"
stage: builddocker
image: ${WAREHOUSE}/base/dood:latest
before_script:
- docker login ${WAREHOUSE} -u "$IMGHUBUSER" -p "$IMGHUBPWD"
script:
- *docker
after_script:
- *relay
artifacts:
reports:
dotenv: build.env
only:
- master
when:
manual

build-bit_live_web:
variables:
PROJECT: bit-live-web
DOMAIN: live.wikibit.com
EXPOSEPORT: 8088
DOCKER_FILE_PATH: "trunk/WikibitLive.Web/Dockerfile"
CONTEXT_PATH: "trunk/"
stage: builddocker
image: ${WAREHOUSE}/base/dood:latest
before_script:
- docker login ${WAREHOUSE} -u "$IMGHUBUSER" -p "$IMGHUBPWD"
script:
- *docker

after_script:
- *relay
artifacts:
reports:
dotenv: build.env
only:
- master
when:
manual

deploy-bit_survey_web:
stage: deploy_dev
before_script:
- *init_k8s
image: ${WAREHOUSE}/base/kubectl:v1.18.12
script:
- *deploy_k8s
needs:
- job: build-bit_survey_web
artifacts: true
only:
- master

deploy-bit_live_web:
stage: deploy_dev
before_script:
- *init_k8s
image: ${WAREHOUSE}/base/kubectl:v1.18.12
script:
- *deploy_k8s
needs:
- job: build-bit_live_web
artifacts: true
only:
- master
# APPLYFILE(文件类型)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${SERVICE_NAME}
namespace: ${NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: ${SERVICE_NAME}
template:
metadata:
labels:
app: ${SERVICE_NAME}
spec:
volumes:
- name: logsmk #volume名字
hostPath:
path: /var/applogs
containers:
- name: ${SERVICE_NAME}
image: ${IMAGE}
imagePullPolicy: IfNotPresent
env:
- name: ENVIRONMENT
valueFrom:
configMapKeyRef:
name: environment
key: ENVIRONMENT
ports:
- containerPort: ${EXPOSEPORT}
resources:
requests:
memory: 100Mi
cpu: 100m
limits:
memory: 1024Mi
cpu: 1000m
livenessProbe:
httpGet:
path: /get/check
port: ${EXPOSEPORT}
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /get/check
port: ${EXPOSEPORT}
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 5
periodSeconds: 15
volumeMounts:
- name: logsmk
mountPath: /var/applogs/

---
apiVersion: v1
kind: Service
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
spec:
ports:
- port: 80
protocol: TCP
targetPort: ${EXPOSEPORT} #pod对外的端口
selector:
app: ${SERVICE_NAME} #匹配到此label的pod的8002端口,会加入到此k8s lb中
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ${SERVICE_NAME}
namespace: ${NAMESPACE}
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
spec:
rules:
- host: ${DOMAIN}
http:
paths:
- path: /
backend:
serviceName: ${SERVICE_NAME}
servicePort: 80
# KUBE_CONFIG(文件类型)

file

飞书机器人告警

1
2
.remind: &remind |
curl -X POST -H 'Content-Type: application/json' --url 'https://open.feishu.cn/open-apis/bot/v2/hook/3cebab07-xxxx-42ce-b5c2-f03d1a7c41ae' -d "{\"msg_type\": \"interactive\",\"card\": {\"config\": {\"wide_screen_mode\": true},\"header\": {\"title\": {\"tag\": \"plain_text\",\"content\": \"DevOps In Testing Environment\"},\"template\": \"blue\"},\"elements\": [{\"tag\": \"markdown\",\"content\": \"**项目名称**:${CI_PROJECT_NAME}/${CI_PROJECT_TITLE} \n**项目地址**:[${CI_PROJECT_URL}](${CI_PROJECT_URL})\n**提交作者**:${CI_COMMIT_AUTHOR} \n**触发作业用户**:${GITLAB_USER_NAME}\n****\n**作业分支**:${CI_COMMIT_BRANCH} \n**CODE SHA**:${CI_COMMIT_SHA}\n**任务ID**:${CI_JOB_ID}\n**任务名称/所处阶段**:${CI_JOB_NAME}/${CI_JOB_STAGE}\n**任务运行详情**:[${CI_JOB_URL}](${CI_JOB_URL}) \n**流水线ID**:${CI_PIPELINE_ID} \n**流水线运行详情**: [${CI_PIPELINE_URL}](${CI_PIPELINE_URL})\"}]} }"

feishu

发布静态站点至aliyun OSS

一些静态资源或者vue站点构建出来的h5站点,需要发布在oss上面利用oss的全球加速功能提高访问速度。基于OSS的CICD发布面临挑战。以下是我个人的解决方案 供参考
ossutil支持通过Windows、Linux和macOS系统以命令行方式管理OSS数据。

1
wget http://gosspublic.alicdn.com/ossutil/1.7.7/ossutil64

编写Dockerfile

1
2
3
FROM registry.cn-shanghai.aliyuncs.com/wikifx/base:alpine-glibc-Shanghai
COPY ossutil64 /bin/
RUN chmod +x /bin/ossutil64

编写.gitlab-ci.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#自定义阶段
stages:
- deploy_oss

variables:
WAREHOUSE: 192.168.200.177
IMAGE: ${WAREHOUSE}/dev_image/${PROJECT}:${CI_PIPELINE_ID}
endPoint: oss-cn-shanghai.aliyuncs.com

.deploy_k8s: &deploy_oss |
ossutil64 config -e ${endPoint} -i ${accessKeyID} -k ${accessKeySecret} -L CH --loglevel debug -c ~/.ossutilconfig
ossutil64 -c ~/.ossutilconfig cp -r -f ./Contentgj/ oss://fxeye-head/Contentgj/

#config 创建配置文件来存储OSS访问信息
#-e endPoint 选择地域 阿里云各OSS全部地域说明:https://help.aliyun.com/document_detail/31837.htm
#-i accessKeyID/ -k accessKeySecret阿里云用户使用云产品的 OpenAPI 接口验权密钥
#-L 设置ossutil工具的语言。取值如下:
#CH(默认值):中文。设置为CH时,请确保您的系统编码方式为UTF-8。
#EN:英文。
#--loglevel debug 日志级别
#-c ossutil工具的配置文件路径,ossutil启动时将从配置文件读取配置。

#cp -r -f 复制文件递归操作,强制操作,不进行询问提示。
#./Contentgj/ git仓库中的文件路径
#oss://fxeye-head/Contentgj/ 远程Bucket中的目录

.remind: &remind |
curl -X POST -H 'Content-Type: application/json' --url 'https://open.feishu.cn/open-apis/bot/v2/hook/3cebab07-04f7-42ce-b5c2-f03d1a7c41ae' -d "{\"msg_type\": \"interactive\",\"card\": {\"config\": {\"wide_screen_mode\": true},\"header\": {\"title\": {\"tag\": \"plain_text\",\"content\": \"DevOps In Testing Environment\"},\"template\": \"blue\"},\"elements\": [{\"tag\": \"markdown\",\"content\": \"**项目名称**:${CI_PROJECT_NAME}/${CI_PROJECT_TITLE} \n**项目地址**:[${CI_PROJECT_URL}](${CI_PROJECT_URL})\n**提交作者**:${CI_COMMIT_AUTHOR}\n**代码版本**:${CI_COMMIT_SHA:0:8}\n**CODE SHA**:${CI_COMMIT_SHA}\n**版本备注**:${CI_COMMIT_MESSAGE}\n****\n**作业分支**:${CI_COMMIT_BRANCH} \n**触发作业用户**:${GITLAB_USER_NAME}\n**任务ID**:${CI_JOB_ID}\n**任务名称/所处阶段**:${CI_JOB_NAME}/${CI_JOB_STAGE}\n**任务运行详情**:[${CI_JOB_URL}](${CI_JOB_URL}) \n**流水线ID**:${CI_PIPELINE_ID} \n**流水线运行详情**: [${CI_PIPELINE_URL}](${CI_PIPELINE_URL})\"}]} }"

deploy_oss-api:
stage: deploy_oss
before_script:
- *remind
image: ${WAREHOUSE}/base/ossutil64:1.7.7
script:
- *deploy_oss
when:
manual

uploadoss