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

使用Artifactory Webhooks和Docker进行持续部署

持续部署(CD)需要设置基础架构和自动化,以便使用来自主分支的最新代码更改来更新解决方案。这就是我们所说的“液体软件”。完全自动化使您的部署无缝、更少出错、更快,并且使反馈循环更短,因为您现在可以在每次更改后进行部署。

实现持续部署需要以下要素:

  • 持续集成(CI),例如Jenkins或JFrog Pipelines,用于验证/构建新版本。
  • 工件管理器,例如JFrog Artifactory,来存储您的工件,并为您的部署目标(服务器、智能设备、计算机)提供新版本。
  • 部署代理这将处理新工件并使其可操作(停止当前服务器、下载二进制文件、启动服务器)。代理有两种类型:
    • :在目标上运行的代理
    • :在一个地方运行的代理远程更新目标

拉式和推式部署模型各有优缺点,您也可以将两者结合使用。pull模型最显著的缺点是代理不知道二进制存储中的更改,因此它不知道何时触发更新。推送模型的缺点之一是安全性,因为目标需要确保部署代理经过身份验证,并且只能执行授权的操作。

在这篇博文中,你将学习如何创建一个推/拉解决方案。我们将经历以下步骤:推送Docker映像进行验证,将其推广到生产环境,最后使用JFrog人工网络钩子触发将其部署到生产服务器。

设置Artifactory

首先,您需要一个正在运行的Artifactory服务器。如果你还没有,您可以免费创建一个云实例

首先创建两个Docker存储库docker-local-stagingdocker-local-prod。创建一个新的存储库:

创建新的Docker存储库

在新的存储库窗口中:

  1. 选择码头工人
  2. 输入“docker-local-staging”作为存储库密钥
  3. 点击“保存并完成”
  4. 重复“docker-local-prod”

现在您有了两个空存储库,继续设置webhook。导航到管理选项卡|一般|人则然后点击"新webhook”。填写表格如下:

创建新的webhook

请注意:在本例中,URL设置为“http://host.docker.internal:7979/”。这是因为webhook处理程序将在本地主机和端口7979上运行。这里的“host. Docker .internal”主机名用于从Docker容器访问主机。在生产环境中,您可能需要将其更改为您的生产服务器URL和您选择的端口。

在secret字段中,您可以输入任何您想要的字符串,它将在HTTP报头“X-jfrog-event-auth”中发送,因此您可以验证查询来自可信来源。

Docker标签最近升级了

选择“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过程的最后一英里。你自己试试吧!并与我们联系您的反馈。