引言
持续部署(CD) 是在持续集成的基础上,把集成代码或构建产物自动化部署到测试或生产环境。这就是我们所说的“流动软件”。完全自动化可以使您的部署无缝、更少的出错几率、更快,并且可以缩短反馈循环,因为您现在可以在每次更改之后进行部署。
实现持续部署需要以下要素:
持续集成(CI),如Jenkins或JFrog Pipeline,用于构建/验证新版本。
制品管理器,如JFrog Artifactory,用于存储制品,并提供新版本的部署目标(服务器、智能设备)。
一个部署代理,控制新版本制品的相关运维操作 (停止当前服务器、下载二进制文件、启动服务器)。代理有两种类型:
拉取方式: 在目标上运行的代理
推方式: 在任意集中服务器上运行的代理,远程更新目标服务
两种方式的对比:
拉和推部署模型各有优缺点,您也可以同时使用这两种模型。拉模型最显著的缺点是代理不知道二进制存储中的更改,因此它不知道何时触发更新。推送模型的一个缺点是安全性,因为目标需要确保部署代理经过身份验证,并且只能执行授权执行的操作。
在本次分享中,我们会分享如何创建一个推/拉的解决方案。我们将一步一步实现从构建推送Docker镜像到注册中心进行验证,并将其升级生产环境,最后使用JFrog Artifactory webhook来触发将其部署到我们的生产服务器。
1.搭建制品库Artifactory
首先,您需要一个运行的Artifactory服务器。如果您还没有云实例,您可以免费创建一个云实例。https://jfrog.com/artifactory/start-free/#saas
首先创建两个Docker仓库:Docker-local-staging和Docker-local-prod。
在new repository窗口中:
- 选择Docker
- 输入“docker-local-staging”作为key
- 点击“保存并完成”
- 重复上述步骤创建“docker-local-prod”
现在你有了两个空的存储库,继续设置webhook。导航到管理菜单 Admin |General| Webhooks,点击“新建webhook“像这样填写:
注意:在这个例子中,URL设置为" http://host.docker.internal:7979/ "。这是因为webhook处理程序将运行在本地主机和端口7979上。这里的host.docker.internal主机名是用来从Docker容器到达主机的。在生产环境中,您可能需要将其更改为您的生产服务器URL和您选择的端口, Artifactory 当文件有变更会主动通知该地址所执行的服务。
在secret字段中,您可以输入任何您想要的字符串,它将以HTTP header“X-jfrog-event-auth”形式发送到目标服务,这样您就可以验证查询是否来自可信的源。
选择“Docker tag was promoted”事件。在Artifactory中,Docker镜像可以被升级(晋级,代表测试验证通过,将该镜像升级为更高成熟度状态),这需要在不修改内容的情况下将Docker镜像从一个仓库移动到另一个仓库。这可以确保在准备阶段测试的镜像是将验证通过并是即将投产的镜像。
点击“Select Repositories”,然后选择要从中提升镜像的仓库。你也可以在“Include Patterns”部分添加一个过滤器来匹配你的Docker镜像清单。
2创建Webhook 处理程序
webhook处理程序将在生产服务器上运行,并将接收一个包含变更事件信息的HTTP请求。在上述镜像升级的情况下,它的请求数据将看起来像这样:
webhook处理程序需要做到以下操作:
- 读取并解析HTTP消息体。
- 验证Docker镜像和仓库。即使你在Artifactory的webhook设置中添加了过滤器,服务器也应该总是验证请求输入。
- 拉去最新的Docker镜像。
- 停止正在运行的容器(如果存在的话)。
- 启动新版本。
下面是处理程序的核心逻辑。完整的代码示例可以在Github中找到。
https://github.com/jfrog/project-examples/tree/master/webhook-example
func main() {
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
p, err := readPayload(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Printf("Payload reading error: %+v", err)
return
}
if !isMyServerEvent(p) {
http.Error(w, "Bad event", http.StatusBadRequest)
log.Printf("Unexpected event %+v", p)
return
}
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
log.Printf("New client error: %+v", err)
return
}
err = pullLatestVersion(cli, ctx)
if err != nil {
log.Printf("Pull error: %+v", err)
return
}
err = stopRunningContainer(cli, ctx)
if err != nil {
if client.IsErrNotFound(err){
log.Printf("Container does not exists")
} else {
log.Printf("Stop error: %+v", err)
return
}
}
err = startContainer(cli, ctx)
if err != nil {
log.Printf("Start error: %+v", err)
} else {
log.Printf("Container updated ")
}
})
http.ListenAndServe(":8081", nil)
}
它使用多个开源库:
- golang内置的http server
- docker golang SDK
其他部分代码请查看github源码,包含输入信息检查,拉取最新镜像,更新启动新的容器等
2 构建并推送docker images(demo 应用)
使用以下简单的golang web服务器进行测试: server.go
测试启动:go run serve.go
一个很简单测试服务,当你在浏览器中加载“http://localhost:8080”时打印出“Hello world”。
以下是这个应用程序的Dockerfile (大部分来自VSCode的golang Dockerfile模板):
用以下命令构建dockerfile。这在CI持续集成过程中应该是自动化的(基于JFrog CLI)。
docker build . -t localhost:8082/docker-local-staging/helloworld
jfrog rt docker-push localhost:8082/docker-local-staging/helloworld docker-local-staging --url http://localhost:8082/artifactory --user admin --password password
jfrog rt docker-promote helloworld docker-local-staging docker-local-prod --copy --user admin --password password --url http://localhost:8082/artifactory
jfrog rt docker-promote该命令将触发以下流程:
- Artifactory将Docker镜像复制到Docker-local-prod存储库中。
- Artifactory通过HTTP请求调用Webhook。
- Webhook坐在服务器获取最新版本。
- 它会杀死正在运行的服务器(如果存在的话)。
- 用最新的更改并启动新的服务。
如果你完成到这里,恭喜您,您已经完成了一个自动化部署方案!!!
4.一些建议
希望上面的指南能帮助你开始实现持续部署和使用webhook。还有许多附加的功能可以添加。以下是一些建议:
- 在CI环境中执行所有Docker / Jfrog CLI命令。例如,使用包含“#prod”的提交消息,使开发人员能够进行部署。
- 使用容器编排。进行构建发布Docker命令,比如使用Kubernetes、Docker swarm或者一些云提供商SDK。
- 提高安全性。您可以向来自Artifactory的HTTP查询添加一个自定义头,以确保该查询不会由发现您的开放端口并意外触发部署动作。
- 尝试通过为“docker push”事件创建webhook,自动化分段部署。