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

持续部署(CD)需要设置基础架构和自动化,以便使用来自主分支的最新代码更改来更新解决方案。这就是我们所说的“液体软件”。完全自动化使您的部署无缝、更少出错、更快,并且使反馈循环更短,因为您现在可以在每次更改后进行部署。
实现持续部署需要以下要素:
- 持续集成(CI),例如Jenkins或JFrog Pipelines,用于验证/构建新版本。
- 工件管理器,例如JFrog Artifactory,来存储您的工件,并为您的部署目标(服务器、智能设备、计算机)提供新版本。
- 部署代理这将处理新工件并使其可操作(停止当前服务器、下载二进制文件、启动服务器)。代理有两种类型:
- 拉:在目标上运行的代理
- 推:在一个地方运行的代理远程更新目标
拉式和推式部署模型各有优缺点,您也可以将两者结合使用。pull模型最显著的缺点是代理不知道二进制存储中的更改,因此它不知道何时触发更新。推送模型的缺点之一是安全性,因为目标需要确保部署代理经过身份验证,并且只能执行授权的操作。
在这篇博文中,你将学习如何创建一个推/拉解决方案。我们将经历以下步骤:推送Docker映像进行验证,将其推广到生产环境,最后使用JFrog人工网络钩子触发将其部署到生产服务器。
设置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. log "。, "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存储库。
函数main() {http。HandleFunc("/");ResponseWriter, r *http. request) {ctx:= context.Background() p, err:= readPayload(r)如果err != nil {http. requestError(w, Error .Error(), http.StatusBadRequest)日志。Printf("Payload reading error: %+v", err) return} if !isMyServerEvent(p) {http。错误(w, "Bad event", http.StatusBadRequest)日志。Printf("意外事件%+v", p) return} cli, err:= client. newclientwithopts (client. newclientwithopts)。from menv, client.WithAPIVersionNegotiation())如果err != nil{日志。Printf("新客户端错误:%+v", err) return} err = pullLatestVersion(cli, ctx)如果err !Printf("把错误:% + v”,犯错)返回}呃= stopRunningContainer (cli, ctx)如果犯错! = nil{如果client.IsErrNotFound (err){日志。Printf("容器不存在")}else{日志。Printf("停止错误:% + v”,犯错)返回}}呃= startContainer (cli, ctx)如果犯错! = nil{日志。Printf("启动错误:%+v", err)} else{日志。Printf("Container updated ")}})ListenAndServe(":8081", nil)}
它使用多个库:
- golang内置http服务器
- docker golang SDK
下面的方法检查事件有效负载内容。它检查有效负载的各个字段,以确定消息的内容。它还检查您在webhook创建表单上键入的密码。有效负载结构定义可在这个Github存储库。
函数isMyServerEvent(*http。请求,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”,这样您就不需要这个了。
命令行*客户端。客户端,ctx context.Context)错误{authConfig:= types。AuthConfig{用户名:"admin",密码:" Password ",} encodedJSON, _:= json.Marshal(AuthConfig) _, err:= cli。imageppull (ctx, imageName, types)ImagePullOptions {RegistryAuth: base64.URLEncoding。在codeToString( encodedJSON)}) if err != nil { log.Printf("Pull error: %+v", err) return err } return nil }
然后停止正在运行的服务器:
stopRunningContainer(cli *client。客户端,ctx上下文。上下文)错误{返回cli。ContainerRemove(ctx, containerName, types)ContainerRemoveOptions{Force: true})}
创建并启动容器(在golang docker SDK上没有“docker run”):
startContainer(cli *client。Client, ctx context.Context) error {resp, err:= cli。ContainerCreate (ctx,容器。配置{Image: imageName,}和容器。HostConfig{PortBindings: nat. portmap {"8080/tcp": []nat。PortBinding{{HostIP: "0.0.0.0", HostPort: "8080",},},}, nil, nil, containerName)如果err != nil {return err} err = cli。ContainerStart (ctx,分别地。ID, types.ContainerStartOptions{})如果err != nil{返回err}返回nil}
这就完成了webhook处理程序,让我们试一下。
建立/推广你的形象
使用以下简单的golang web服务器进行测试:
Package main import ("fmt" "net/http") function main() {http。HandleFunc("/");ResponseWriter, r *http.Request) {流(w,“Hello World, % s !”,r.URL.Path [1:])}) http。ListenAndServe(":8080", nil)}
使用以下go命令运行:
去,运行服务器,去
它很简单,当您在浏览器中加载“http://localhost:8080”时打印出“Hello world”。
以下是这个应用程序的Dockerfile(大部分来自VSCode的golang Dockerfile模板):
运行apk添加——no-cache git WORKDIR /go/src/app COPY . .运行命令go install -v ./…从alpine:最新运行apk——no-cache添加ca-certificates拷贝——FROM =builder /go/bin/app /app ENTRYPOINT ./app标签名称=blogpostevent版本=0.0.1
使用以下命令构建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 password
现在你可以让你的QA同事测试你的服务器(他们应该告诉你一切正常,因为我们没有制造任何bug,但在某些罕见的情况下可能会发生)。一旦完成了这些,你想要上线,你只需要执行一个命令:
——copy——user admin——password——password——url http://localhost:8082/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 webhook和全自动工作流的帮助下实现CICD过程的最后一英里。你自己试试吧!并与我们联系您的反馈。