提交 0456a789 编写于 作者: 阳明的博客's avatar 阳明的博客

添加devops文章

上级 cb3dec98
......@@ -99,5 +99,5 @@
* [Harbor](docs/63.Harbor.md)
* [Gitlab](docs/64.Gitlab.md)
* [Gitlab CI](docs/65.Gitlab CI.md)
* [Devops](docs/66/devops.md)
* [Devops](docs/66.devops.md)
......@@ -199,7 +199,7 @@ REVISION CHANGE-CAUSE
上生产时,我们最好通过设置Deployment的`.spec.revisionHistoryLimit`来限制最大保留的`revision number`,比如15个版本,回滚的时候一般只会回滚到最近的几个版本就足够了。其实`rollout history`中记录的`revision`都和`ReplicaSets`一一对应。如果手动`delete`某个ReplicaSet,对应的`rollout history`就会被删除,也就是还说你无法回滚到这个`revison`了。
`roolout history``ReplicaSet`的对应关系,可以在`kubectl describe rs $RSNAME`返回的`revision`字段中得到,这里的`revision`就对应着`roolout history`返回的`revison`
`rollout history``ReplicaSet`的对应关系,可以在`kubectl describe rs $RSNAME`返回的`revision`字段中得到,这里的`revision`就对应着`rollout history`返回的`revison`
同样我们可以使用下面的命令查看单个`revison`的信息:
......
......@@ -330,12 +330,14 @@ container('构建 Docker 镜像') {
credentialsId: 'dockerhub',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
echo "3. 构建 Docker 镜像阶段"
sh """
docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
container('docker') {
echo "3. 构建 Docker 镜像阶段"
sh """
docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
}
}
}
```
......@@ -505,99 +507,284 @@ stage('运行 Kubectl') {
```
第五阶段:运行 Helm 工具,就是直接使用 Helm 来部署应用了,现在有了上面的基本的资源对象了,现在要创建 Chart 模板就相对容易了,首先确保我们本地安装有 Helm Client,使用下面的命令创建项目
第五阶段:运行 Helm 工具,就是直接使用 Helm 来部署应用了,现在有了上面的基本的资源对象了,要创建 Chart 模板就相对容易了,Chart 模板仓库地址:[https://github.com/cnych/polling-helm](https://github.com/cnych/polling-helm),我们可以根据`values.yaml`文件来进行自定义安装,模板中我们定义了可以指定使用外部数据库服务或者内部独立的数据库服务,具体的我们可以去看模板中的定义。首先我们可以先使用这个模板在集群中来测试下。首先在集群中 Clone 上面的 Chart 模板
```shell
$ helm create polling-helm
Creating polling-helm
$ tree polling-helm
polling-helm
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
└── values.yaml
2 directories, 7 files
$ git clone https://github.com/cnych/polling-helm.git
```
然后将上面的 k8s.yaml 文件我们拆分成四个文件放置到 templates 目录下面,把 polling-server 分成 deployment.yaml 和 service.yaml 文件,而数据库的我们单独创建一个文件夹,然后放置在文件夹下面:
```shell
$ tree polling-helm/templates
polling-helm/templates
├── NOTES.txt
├── _helpers.tpl
├── database
│   ├── database-svc.yaml
│   └── database-ss.yaml
├── deployment.yaml
├── ingress.yaml
└── service.yaml
1 directory, 7 files
然后我们使用内部的数据库服务,新建一个 custom.yaml 文件来覆盖 values.yaml 文件中的值:
```yaml
persistence:
enabled: true
persistentVolumeClaim:
database:
storageClass: "database"
database:
type: internal
internal:
database: "polling"
# 数据库用户
username: "polling"
# 数据库用户密码
password: "polling321"
```
然后就可以定制模板了,首先针对主要的 deployment.yaml 文件,把所有能够定制的数据都通过 values 或者 template 来进行渲染
可以看到我们这里使用了一个名为`database`的 StorgeClass 对象,所以还得创建先创建这个资源对象
```yaml
apiVersion: extensions/v1beta1
kind: Deployment
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: {{ include "polling-helm.fullname" . }}
labels:
app: {{ include "polling-helm.name" . }}
chart: {{ include "polling-helm.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: {{ include "polling-helm.name" . }}
release: {{ .Release.Name }}
spec:
restartPolicy: Always
imagePullSecrets:
- name: myreg
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
name: api
env:
- name: DB_HOST
value: {{ .Values.db.host }}
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: polling_app
- name: DB_USER
value: polling
- name: DB_PASSWORD
value: polling321
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
name: database
provisioner: fuseim.pri/ifs
```
然后我们就可以在 Chart 根目录下面安装应用,执行下面的命令:
```shell
$ helm upgrade --install polling -f custom.yaml . --namespace course
Release "polling" does not exist. Installing it now.
NAME: polling
LAST DEPLOYED: Sat May 4 23:31:42 2019
NAMESPACE: course
STATUS: DEPLOYED
RESOURCES:
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
polling-polling-api-6b699478d6-lqwhw 0/1 ContainerCreating 0 0s
polling-polling-ui-587bbfb7b5-xr2ff 0/1 ContainerCreating 0 0s
polling-polling-database-0 0/1 Pending 0 0s
==> v1/Secret
NAME TYPE DATA AGE
polling-polling-database Opaque 1 0s
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
polling-polling-api ClusterIP 10.109.19.220 <none> 8080/TCP 0s
polling-polling-database ClusterIP 10.98.136.190 <none> 3306/TCP 0s
polling-polling-ui ClusterIP 10.108.170.43 <none> 80/TCP 0s
==> v1beta2/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
polling-polling-api 1 1 1 0 0s
polling-polling-ui 1 1 1 0 0s
==> v1/StatefulSet
NAME DESIRED CURRENT AGE
polling-polling-database 1 1 0s
==> v1beta1/Ingress
NAME HOSTS ADDRESS PORTS AGE
polling-polling-ingress ui.polling.domain 80 0s
NOTES:
1. Get the application URL by running these commands:
http://ui.polling.domain
You have new mail in /var/spool/mail/root
```
> 注意我们这里安装也是使用的`helm upgrade`命令,这样有助于安装和更新的时候命令统一。
安装完成后,查看下 Pod 的运行状态:
```shell
$ kubectl get pods -n course
NAME READY STATUS RESTARTS AGE
polling-polling-api-6b699478d6-lqwhw 1/1 Running 0 3m
polling-polling-database-0 1/1 Running 0 3m
polling-polling-ui-587bbfb7b5-xr2ff 1/1 Running 0 3m
```
然后我们可以在本地`/etc/hosts`里面加上`http://ui.polling.domain`的的映射,这样我们就可以通过这个域名来访问我们安装的应用了,可以注册、登录、发表投票内容了:
![polling app](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/oblr3.png)
这样我们就完成了使用 Helm Chart 安装应用的过程,但是现在我们使用的包还是直接使用的 git 仓库中的,平常我们正常安装的时候都是使用的 Chart 仓库中的包,所以我们需要将该 Chart 包上传到一个仓库中去,比较幸运的是我们的 Harbor 也是支持 Helm Chart 包的。我们可以选择手动通过 Harbor 的 Dashboard 将 Chart 包进行上传,也可以通过使用`Helm Push`插件:
```shell
$ helm plugin install https://github.com/chartmuseum/helm-push
Downloading and installing helm-push v0.7.1 ...
https://github.com/chartmuseum/helm-push/releases/download/v0.7.1/helm-push_0.7.1_linux_amd64.tar.gz
Installed plugin: push
```
当然我们需要首先将 Harbor 提供的仓库添加到 helm repo 中,由于是私有仓库,所以在添加的时候我们需要添加用户名和密码:
```shell
$ helm repo add course https://registry.qikqiak.com/chartrepo/course --username=<harbor用户名> --password=<harbor密码>
"course" has been added to your repositories
```
这里的 repo 的地址是**<Harbor URL>/chartrepo/<Harbor中项目名称>**,Harbor 中每个项目是分开的 repo,如果不提供项目名称,则默认使用`library`这个项目。
> 需要注意的是如果你的 Harbor 是采用的自建的 https 证书,这里就需要提供 ca 证书和私钥文件了,否则会出现证书校验失败的错误`x509: certificate signed by unknown authority`。我们这里是通过`cert-manager`为 Harbor 提供的一个信任的 https 证书,所以没有指定 ca 证书相关的参数。
然后我们将上面的`polling-helm`这个 Chart 包上传到 Harbor 仓库中去:
```shell
$ helm push polling-helm course
Pushing polling-0.1.0.tgz to course...
Done.
```
这个时候我们登录的 Harbor 仓库中去,查看 course 这个项目下面的`Helm Charts`就可以发现多了一个 polling 的应用了:
![helm chart](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/96e57.png)
我们也可以在右下角看到有添加仓库和安装 Chart 的相关命令。
到这里 Helm 相关的工作就准备好了。那么我们如何在 Jenkins Pipeline 中去使用 Helm 呢?我们可以回顾下,我们平时的一个 CI/CD 的流程:开发代码 -> 提交代码 -> 触发镜像构建 -> 修改镜像tag -> 推送到镜像仓库中去 -> 然后更改 YAML 文件镜像版本 -> 使用 kubectl 工具更新应用。
现在我们是不是直接使用 Helm 了,就不需要去手动更改 YAML 文件了,也不需要使用 kubectl 工具来更新应用了,而是只需要去覆盖下 helm 中的镜像版本,直接 upgrade 是不是就可以达到应用更新的结果了。我们可以去查看下 chart 包的 values.yaml 文件中关于 api 服务的定义:
```yaml
api:
image:
repository: cnych/polling-api
tag: 0.0.7
pullPolicy: IfNotPresent
```
我们是不是只需要将上面关于 api 服务使用的镜像用我们这里 Jenkins 构建后的替换掉就可以了,这样我们更改上面的最后`运行 Helm`的阶段如下:
```groovy
stage('运行 Helm') {
container('helm') {
echo "更新 polling 应用"
sh """
helm upgrade --install polling polling --set persistence.persistentVolumeClaim.database.storageClass=database --set database.type=internal --set database.internal.database=polling --set database.internal.username=polling --set database.internal.password=polling321 --set api.image.repository=${image} --set api.image.tag=${imageTag} --set imagePullSecrets[0].name=myreg --namespace course
"""
}
}
```
当然我们可以将需要更改的值都放入一个 YAML 之中来进行修改,我们这里通过`--set`来覆盖对应的值,这样整个 API 服务的完整 Jenkinsfile 文件如下所示:
```groovy
def label = "slave-${UUID.randomUUID().toString()}"
def helmLint(String chartDir) {
println "校验 chart 模板"
sh "helm lint ${chartDir}"
}
def helmInit() {
println "初始化 helm client"
sh "helm init --client-only --stable-repo-url https://mirror.azure.cn/kubernetes/charts/"
}
def helmRepo(Map args) {
println "添加 course repo"
sh "helm repo add --username ${args.username} --password ${args.password} course https://registry.qikqiak.com/chartrepo/course"
println "更新 repo"
sh "helm repo update"
println "获取 Chart 包"
sh """
helm fetch course/polling
tar -xzvf polling-0.1.0.tgz
"""
}
def helmDeploy(Map args) {
helmInit()
helmRepo(args)
if (args.dry_run) {
println "Debug 应用"
sh "helm upgrade --dry-run --debug --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set database.type=internal --set database.internal.database=polling --set database.internal.username=polling --set database.internal.password=polling321 --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
} else {
println "部署应用"
sh "helm upgrade --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set database.type=internal --set database.internal.database=polling --set database.internal.username=polling --set database.internal.password=polling321 --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
echo "应用 ${args.name} 部署成功. 可以使用 helm status ${args.name} 查看应用状态"
}
}
podTemplate(label: label, containers: [
containerTemplate(name: 'maven', image: 'maven:3.6-alpine', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true)
], volumes: [
hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
def dockerRegistryUrl = "registry.qikqiak.com"
def imageEndpoint = "course/polling-api"
def image = "${dockerRegistryUrl}/${imageEndpoint}"
stage('单元测试') {
echo "1.测试阶段"
}
stage('代码编译打包') {
try {
container('maven') {
echo "2. 代码编译打包阶段"
sh "mvn clean package -Dmaven.test.skip=true"
}
} catch (exc) {
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
container('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
container('docker') {
echo "3. 构建 Docker 镜像阶段"
sh """
docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
}
}
}
stage('运行 Helm') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
container('helm') {
echo "4. [INFO] 开始 Helm 部署"
helmDeploy(
dry_run : false,
name : "polling",
chartDir : "polling",
namespace : "course",
tag : "${imageTag}",
image : "${image}",
username : "${DOCKER_HUB_USER}",
password : "${DOCKER_HUB_PASSWORD}"
)
echo "[INFO] Helm 部署应用成功..."
}
}
}
}
}
```
由于我们没有将 chart 包放入到 API 服务的代码仓库中,这是因为我们这里使用的 chart 包涉及到两个应用,一个 API 服务,一个是前端展示的服务,所以我们这里是通过脚本里面去主动获取到 chart 包来进行安装的,如果 chart 包跟随代码仓库一起管理当然就要简单许多了。
现在我们去更新 Jenkinsfile 文件,然后提交到 gitlab 中,然后去观察下 Jenkins 中的构建是否成功,我们重点观察下 Helm 阶段:
![jenkins helm console](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/yfilp.png)
当然我们还可以去做一些必要的判断工作,比如根据分支判断是否需要自动部署等等,同样也可以切换到 Blue Occean 界面查看构建结果。
![jenkins blue occean](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/sq24p.jpg)
现在大家可以尝试去修改下代码,然后提交代码到 gitlab 上,观察下 Jenkins 是否能够自动帮我们完成整个 CI/CD 的过程。
作业:现在还有一个前端展示的项目:[http://git.qikqiak.com/course/polling-app-client.git](http://git.qikqiak.com/course/polling-app-client.git),大家针对这个项目使用上面的 gitlab + jenkins + harbor + helm 来完成一个 Jenkins Pipeline 流水线的编写,尝试去修改下前端页面内容,看是否能够生效。
gitlab-ci-k8s-demo @ 62ec25b6
Subproject commit 62ec25b629f8a7c174c40916c38293694bcf6e5d
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册