使用Artifactory Webhooks和Docker实现持续部署

持续部署(CD)需要设置基础设施和自动化,以便使用来自主分支的最新代码更改来更新解决方案。这就是我们所说的“液体软件”.完全自动化使您的部署无缝,更不容易出错,更快,并且它使反馈循环更短,因为您现在可以在每次更改后进行部署。
实现持续部署需要以下要素:
- 持续集成(CI),例如Jenkins或JFrog pipeline,用于验证/构建新版本。
- 工件管理器,例如JFrog Artifactory,以存储您的工件,并使用新版本提供您的部署目标(服务器、智能设备、计算机)。
- 部署代理这将处理新的工件并使其可操作(停止当前服务器,下载二进制文件,启动服务器)。有两种类型的代理:
- 拉:在目标机上运行的代理
- 推:运行在单一位置的远程更新目标的代理
pull和push部署模型各有利弊,您也可以将两者结合使用。pull模型最显著的缺点是代理不知道二进制存储中的变化,因此它不知道何时触发更新。推送模型的一个缺点是安全性,因为目标需要确保部署代理经过身份验证,并且只能做授权它做的事情。
在这篇博客文章中,你将学习如何创建一个推/拉解决方案。我们将详细介绍推送Docker映像进行验证、将其推广到生产环境以及最后使用JFrog Artifactory webhook触发将其部署到我们的生产服务器。
设置Artifactory
首先,您需要一个正在运行的Artifactory服务器。如果你还没有,您可以免费创建一个云实例.
首先创建两个Docker存储库:docker-local-staging而且docker-local-prod.创建一个新的存储库:

在新的存储库窗口中:
- 选择码头工人
- 输入“docker-local-staging”作为存储库密钥
- 点击“保存并完成”
- 重复执行" docker-local-prod "
现在你有了两个空的存储库,继续设置webhook。导航到管理选项卡|一般|人则点击"新webhook”。像这样完成表单:

请注意:在本例中,URL设置为"http://host.docker.internal:7979/”。这是因为webhook处理程序将运行在本地主机和端口7979上。这里的“host. Docker .internal”主机名用于从Docker容器到达主机。在生产环境中,您可能需要将此更改为您的生产服务器URL和您选择的端口。
在secret字段中,您可以输入您想要的任何字符串,它将在HTTP报头“X-jfrog-event-auth”中发送,因此您可以验证查询来自可信源。

选择“Docker标签得到提升”事件。在Artifactory中,Docker映像可以提升这需要将Docker映像从一个存储库移动到另一个存储库,而不修改其内容。这确保了在暂存中测试的映像就是将在生产中部署的映像。
点击“选择存储库,然后选择您将推广您的图像的存储库。您也可以在“包括模式部分,以匹配Docker映像manifest.json工件浏览器中的路径。

既然在Artifactory端已经设置好了一切,让我们继续构建我们的处理程序。
Webhook处理程序
webhook处理程序将运行在生产服务器上,并将接收一个包含事件有效负载的HTTP请求。在推广的情况下,它将看起来像这样:
{"domain": "docker", "event_type": "promoted", "data": {"image_name": "helloworld", "name": "manifest. "Json ", "path": "helloworld/latest/manifest. Json ", "path": "helloworld/latest/manifest. Json "Json ", "platforms": [], "repo_key": "docker-local-staging", "sha256": "ee34c5c94b4d7d0b319af21a84ebb0bcc16ef01be9a6ee0277329256ecee29b0", "size": 949, "tag": "latest"}}
webhook处理程序需要:
- 读取并解析HTTP消息体。
- 验证Docker映像和存储库。即使你在Artifactory的webhook设置中添加了过滤器,服务器也应该始终验证传入调用。
- 提取最新的Docker映像。
- 停止正在运行的容器(如果存在的话)。
- 启动新版本。
这里是处理程序的核心。完整的代码示例可在这个Github存储库.
Func main() {http。HandleFunc("/", func (w http。ResponseWriter, r *http. request) {ctx:= context.Background() p, err:= readPayload(r) if err != nil {http. request)Error(w, err.Error(), http.StatusBadRequest)日志。Printf("有效负载读取错误:%+v", err)返回}if !错误(w,“坏事件”,http.StatusBadRequest)日志。Printf("意外事件%+v", p) return} cli, err:= client. newclientwithopts(客户端。frommenv, client.WithAPIVersionNegotiation())如果err != nil{日志。Printf("新客户端错误:%+v", err) return} err = pullLatestVersion(cli, ctx)如果err != nil{日志。Printf("Pull error: %+v", err) return} err = stopRunningContainer(cli, ctx) if err !Printf("容器不存在")}else {log。如果err != nil {log. Printf("Stop error: %+v", err) return}} err = startContainer(cli, ctx)Printf("启动错误:%+v", err)} else {log。Printf("Container updated ")}})ListenAndServe(":8081", nil)}
它使用多个库:
- golang内置http服务器
- docker golang SDK
下面的方法检查事件有效负载内容。它检查有效负载的各个字段,以确定消息是关于什么的。它还检查你在webhook创建表单上输入的秘密。有效负载结构定义可在这个Github存储库.
func isMyServerEvent(r *http。Request, p DockerEventPayload) bool{返回p. domain == "docker" && p. eventtype == "promoted" && p. data . imagename == "helloworld" && p. data . repokey == "docker-local-staging" && p. data . tag == "latest" && r.Header.Get("X-JFrog-Event-Auth") == "mysecrets"}
接下来,使用下面的方法使用Docker SDK提取最新的图像。
注意:在源代码中硬编码用户凭证是一种糟糕的做法。在下面的示例中添加用户和密码,以显示如何指定身份验证。但是,你应该用“docker login”来设置你的服务器环境,这样你就不需要这个了。
func pullLatestVersion(命令行*客户端。客户端,ctx context.Context)错误{authConfig:=类型。AuthConfig{用户名:“admin”,密码:“Password”,}encodedJSON, _:= json.Marshal(AuthConfig) _, err:= cli。ImagePull(ctx, imageName,类型。ImagePullOptions {RegistryAuth: base64.URLEncoding。在codeToString( encodedJSON)}) if err != nil { log.Printf("Pull error: %+v", err) return err } return nil }
然后停止正在运行的服务器:
func stopRunningContainer(命令行*客户端。客户端,ctx context.Context)错误{返回cli. context。ContainerRemove(ctx, containerName,类型。ContainerRemoveOptions{Force: true})}
创建并启动容器(在golang docker SDK上没有“docker run”):
func startContainer(命令行*客户端。客户端,ctx context.Context)错误{resp, err:= cli。ContainerCreate (ctx,容器。配置{图像:imageName,}, &容器。HostConfig{PortBindings: nat. portmap {"8080/tcp": []nat. portmap: []PortBinding{{HostIP: "0.0.0.0", HostPort: "8080",},},}, nil, nil, containerName) if err !ContainerStart (ctx,分别地。ID, types.ContainerStartOptions{}) if err != nil{返回err}返回nil}
这就完成了webhook处理程序,让我们尝试一下。
建立/推广你的形象
使用以下简单的golang web服务器进行测试:
包主导入("fmt" "net/http") func main() {http. .HandleFunc("/", func (w http。ResponseWriter, r *http.Request) {fmt. request;流(w,“Hello World, % s !”,r.URL.Path [1:])}) http。ListenAndServe(":8080", nil)}
执行如下go命令:
去发球,去
它很简单,当你在浏览器中加载“http://localhost:8080”时,它会打印出“Hello world”。
下面是这个应用程序的Dockerfile(大部分来自VSCode的golang Dockerfile模板):
从golang:alpine AS builder RUN apk add——no-cache git WORKDIR /go/src/app COPY . .RUN go install -v ./…FROM alpine:latest RUN apk——no-cache add ca-certificates COPY——FROM =builder /go/bin/app /app ENTRYPOINT ./app LABEL Name=blogpostevent Version=0.0.1 EXPOSE 8080
使用以下命令构建dockerfile。这应该在CI过程中自动完成。
Docker构建。- t localhost: 8082 / docker-local-staging / helloworld
使用JFrog CLI将Docker映像推入Artifactory。
Jfrog rt docker-push localhost:8082/docker-local-staging/helloworld docker-local-staging——url http://localhost:8082/artifactory——user admin——password密码
现在你可以让QA同事来测试你的服务器(他们会告诉你一切正常,因为我们没有制造任何bug,但在某些罕见的情况下也会发生)。一旦完成,你想要上线,你只需要执行一个命令:
Jfrog rt docker-promote helloworld docker-local-staging docker-local-prod——copy——user admin——password password——url http://localhost:8082/artifactory
该命令将触发以下过程:
- Artifactory将Docker映像复制到docker-local-prod存储库。
- Artifactory用一个HTTP请求调用Webhook。
- webhook服务器获取最新版本。
- 它会杀死正在运行的服务器(如果存在的话)。
- 它用最新的更改启动服务器。
瞧!您有一个持续部署设置。
前进
希望上面的指南能帮助你开始实现持续部署和使用webhook。可以添加许多附加功能。以下是一些建议:
- 在CI环境中执行所有Docker / Jfrog CLI命令。例如,使用包含“#prod”的提交消息,使开发人员能够部署。
- 使用真正的容器编排。与其发布Docker命令,不如使用Kubernetes、Docker swarm或一些云提供商SDK。
- 提高安全性。你可以在来自Artifactory的HTTP查询中添加一个自定义报头,以确保查询不会从那些发现你的开放端口并触发部署的人发送,这样你的应用程序就会一直关闭。
- 通过为“docker push”事件创建一个webhook来自动化登台部署。
- 在云FaaS或PaaS中部署您的代理
- 当两个升级事件发生得非常接近时,通过聚合两个升级事件来防止“helloworld”容器的多个并行部署
关闭
在这篇博客文章中,我们看到了如何在Artifactory webhooks和完全自动化的工作流的帮助下实现CICD过程的最后一英里。自己试试吧!并向我们提供您的反馈。
