当你写完代码后,点下push按钮,不到一会你的线上应用就完成了更新并且运行了起来,这就是完美的工作流。

准备流程

这样的工作流也称自动化部署,具体的流程:

  • 开发人员推送代码到git仓库
  • git仓库触发webhook请求build服务器
  • build服务器执行构建任务
  • build服务器打包docker并push到docker仓库
  • build服务器登录生产服务器,停止旧服务,pull新镜像并运行

要构建这么一套自动化部署,我们需要有:

  • git仓库
  • 构建服务器
  • docker镜像仓库
  • 生产服务器

git仓库

github,码云,或者私有的gitea、gitlab都是可行的。

构建服务器

构建过程通常会占用较多的服务器资源,如果服务器配置不佳,在生产服务器上进行构建甚至会导致线上运行的其他应用被迫停止。

docker镜像仓库

如果是非开源项目,一般使用私有仓库,可以在一台服务器上使用docker-registry自建私有仓库
推荐使用docker-registry-ui作为可视化ui,轻量级且支持basic auth
没有仓库服务器也可以直接使用阿里云的镜像仓库服务,非企业版是完全免费的。

生产服务器

生产服务器仅需要安装docker,应用的runtime环境一般会打包在image里。

搭建环境

构建服务器

Docker

安装docker,按照官方文档操作(保证版本在17.05以上,因为这个版本开始才支持多阶段构建)。

安装完毕后需要启用远程连接(jenkins会用到)

  1. vim /usr/lib/systemd/system/docker.service
  2. 找到ExecStart,然后在后面添加上-H tcp://0.0.0.0:2376 -H unix://var/run/docker.sock

配置docker远程.png

最后再搭一块网桥,具体作用查看Docker 无法访问主机 localhost

docker network create -d bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 hostNet

Jenkins

自动化部署的核心,构建应用、推送镜像、登录生产服务器更新镜像都靠它解决。

拉取官方镜像,注意是jenkins/jenkins不是jenkins
docker pull jenkins/jenkins

jenkins.sh

#!/bin/bash

name="jenkins"

mkdir -pv /var/data/$name

docker stop $name

docker rm $name

docker run --name $name -d \
 --detach \
 -p 50000:50000 \
 -p 8080:8080 \
 -v /var/data/$name:/var/jenkins_home \
 jenkins/jenkins

写了个shell脚本方便快速重启,/var/jenkins_home存储了jenkins的所有数据,一定要挂载到宿主机里避免重启后丢失。

启动后进入jenkins,按照流程初始化,安装推荐插件即可。
随后需要再安装一些插件,假定我们所有的项目都是利用Docker进行构建,那么理论上不需要任何环境插件(jdk、go、node等等)。

安装Docker plugin插件,用于docker构建。

安装完毕后需要配置docker cloud,在系统配置页面拉到底,能看到cloud模块。

cloud设置.png

点击连接进入,然后新建一个cloud

新建cloud.png

这时在宿主机上创建的网桥就起作用了,它能让docker内的容器通过网桥直接访问宿主机,2376端口则是docker的远程连接端口。

安装Publish Over SSH插件,用于登录生产服务器更新应用。

安装完毕后,在系统配置里找到Publish over SSH的模块,添加一台SSH Server,也就是你的生产服务器。

添加服务器ssh连接.png

生产服务器

安装Docker,同构建服务器。
如果生产服务器还装有MysqlRedis一类的服务或镜像,也可以搭建一个网桥方便容器内应用访问。

搭建项目

现在自动化部署环境已经搭建完毕,尝试搭建一个项目吧,这里以我的一个golang项目为例子。

Dockerfile

FROM golang AS build-env
ENV CGO_ENABLED 0
ENV GO111MODULE on
ENV GOPROXY https://goproxy.io
ADD . /go/src/app
WORKDIR /go/src/app
RUN go mod download && go get github.com/gadelkareem/bee@fix-mod
RUN bee pack -be GOOS=linux
RUN mkdir -v build && tar -zxvf app.tar.gz -C ./build

FROM alpine
COPY --from=build-env /go/src/app/build /var/www/server
WORKDIR /var/www/server
EXPOSE 8080
CMD [ "./app" ]

这是一个多阶段构建
第一个阶段是使用Golang镜像进行构建
第二阶段使用COPY将上一阶段的构建产物复制到此阶段,然后用alpine作为基础镜像运行

好处很明显,jenkins不需要安装任何构建环境,因为构建也是在容器内进行,完全隔离。最终的产物仅包含第二阶段镜像的产物,镜像的大小非常可观。

git webhook

一般的代码库都会提供webhook,以github为例
打开项目 - Settings - Webhooks - Add Webhook

Payload URL填写http://jenkins面板根目录/github-webhook/,例如http://192.168.1.1:8080/github-webhook/

Content type选择application/json

勾选Active

添加完成即可

jenkins

jenkins上新建一个自由风格任务。

源码管理

源码管理选择git,填写仓库路径,选择分支。

然后选择仓库对应的凭证,没有则添加一个。凭证也就是保存好的身份验证信息。

如果是https的仓库的话,使用Username and password类型的凭证,即git账户密码。

如果是git的仓库的话,使用SSH Username with private key类型的凭证,再填写你的私钥。

源码管理.png

触发构建器

勾选GitHub hook trigger for GITScm polling

构建

不需要构建环境,因为我们的构建环境都在docker里,直接进入构建环节。

增加一个Build / Publis Docker Image

cloud选择前面创建好的local cloud,即宿主机的Docker

Image填写镜像名字
我填写为registry.cn-hangzhou.aliyuncs.com/sakuradon/sakuradon-cq:${BUILD_NUMBER}
直接指明了私有仓库,${BUILD_NUMBER}则是jenkins的环境变量,即这次构建的号码,我用它来作为tag

Registry Credentials则是镜像仓库的身份凭证,根据私有仓库填写。

docker构建.png

这一步进行项目的构建打包并推送到镜像仓库。

增加一个Send files or execute commands over SSH

Name选择前面创建好的远程主机SSH连接。

然后在Exec command里写登陆后执行的shell脚本

registry="registry.cn-hangzhou.aliyuncs.com"
image="${registry}/sakuradon/sakuradon-cq"
name="sakuradon-cq"

docker login --username=sakuraidon --password=xxxxxx ${registry}

docker pull ${image}:${BUILD_NUMBER}

docker stop ${name}

docker rm ${name}

docker rmi ${image}:running

docker tag ${image}:${BUILD_NUMBER} ${image}:running

docker run --name ${name} -d \
 -p 8000:8080 \
 ${image}:running

在生产服务器上拉取最新镜像,删除原有容器,再把tag标记为running,最后创建并启动容器。

远程执行.png

测试

webhook

测试1.png

构建

测试2.png

生产服务器

测试3.png