即便一个小项目也有它的CI/CD流水线

640?wx_fmt=jpeg

现如今,使用市面上的一些工具配置一套简单的CI/CD流水线并不是一件难事。给一个副项目弄一套这样的流水线也是一个学习许多东西的好方法。Docker,Gitlab,Portainer这些优秀的组件可以用来搭建这个流水线。
示例项目

640?wx_fmt=png


作为一名法国索菲亚科技园区(位于法国南部)的技术活动组织者,我经常被问到是否有办法知道所有即将举行的活动(会议,灌水,由当地协会组织的聚会等……)。由于此前并没有一个单独的地方列出所有的这些活动,我便开发了https://sophia.events ,这是一个非常简单的网站页面,它会尝试维护一份最新的活动列表。此项目的代码可以在GitLab[1]上找到。
声明:这个项目超级简单,但是项目本身的复杂度并不是本文的重点。这里我们将详细介绍到的CI/CD流水线的各个组件可以用几乎相同的方式应用到更复杂的项目上。它们也非常适合微服务的场景。
快速过一下代码

640?wx_fmt=png


为了简化起见,这里有一份events.json文件,每个新事件均会被添加到里面。该文件的部分内容见下面的代码段(抱歉里面掺杂了一些法语):
 
 
  1. {

  2.  events”: [

  3.    {

  4.      title”: All Day DevOps 2018”,

  5.      desc”: Were back with 100, 30-minute practitioner-led sessions and live Q&A on Slack. Our 5 tracks include CI/CD, Cloud-Native Infrastructure, DevSecOps, Cultural Transformations, and Site Reliability Engineering. 24 hours. 112 speakers. Free online.”,

  6.      date”: 17 octobre 2018, online event”,

  7.      ts”: 20181017T000000”,

  8.      link”: https://www.alldaydevops.com/",

  9.      sponsors”: [{“name”: all-day-devops”}]

  10.    },

  11.    {

  12.      title”: Création dune Blockchain dentreprise (lab) & introduction aux smart contracts”,

  13.      desc”: Venez avec votre laptop ! Nous vous proposons de nous rejoindre pour réaliser la création dun premier prototype dune Blockchain dentreprise (Lab) et avoir une introduction aux smart contracts.”,

  14.    ts”: 20181004T181500”,

  15.    date”: 4 octobre à 18h15 au CEEI”,

  16.    link”: https://www.meetup.com/fr-FR/IBM-Cloud-Cote-d-Azur-Meetup/events/254472667/",

  17.    sponsors”: [{“name”: ibm”}]

  18.    },

  19.    

  20.  ]

  21. }


此文件将会被一个mustache模板[2]渲染并生成最终的网站素材。
Docker多阶段构建
一旦生成了最终的网站素材,它们将会被拷贝到一个Nginx镜像里,该镜像将会被部署到目标机器上。
得益于多阶段构建(multi-stage build),本次构建分为两部分:
  • 网站素材的生成

  • 包含网站素材的最终镜像的创建


用来构建镜像的Dockerfile如下:
 
 
  1. # 生成素材

  2. FROM node:8.12.0-alpine AS build

  3. COPY . /build

  4. WORKDIR /build

  5. RUN npm i

  6. RUN node clean.js

  7. RUN ./node_modules/mustache/bin/mustache events.json index.mustache > index.html


  8. # 构建托管它们的最终镜像

  9. FROM nginx:1.14.0

  10. COPY --from=build /build/*.html /usr/share/nginx/html/

  11. COPY events.json /usr/share/nginx/html/

  12. COPY css /usr/share/nginx/html/css

  13. COPY js /usr/share/nginx/html/js

  14. COPY img /usr/share/nginx/html/img


本地测试
为了测试生成站点,只需克隆该仓库然后运行test.sh脚本即可。它将随后创建出一个镜像并运行一个容器:
 
 
  1. $ git clone git@gitlab.com:lucj/sophia.events.git


  2. $ cd sophia.events


  3. $ ./test.sh

  4. Sending build context to Docker daemon  2.588MB

  5. Step 1/12 : FROM node:8.12.0-alpine AS build

  6. ---> df48b68da02a

  7. Step 2/12 : COPY . /build

  8. ---> f4005274aadf

  9. Step 3/12 : WORKDIR /build

  10. ---> Running in 5222c3b6cf12

  11. Removing intermediate container 5222c3b6cf12

  12. ---> 81947306e4af

  13. Step 4/12 : RUN npm i

  14. ---> Running in de4e6182036b

  15. npm notice created a lockfile as package-lock.json. You should commit this file.

  16. npm WARN www@1.0.0 No repository field.

  17. added 2 packages from 3 contributors and audited 2 packages in 1.675s

  18. found 0 vulnerabilities

  19. Removing intermediate container de4e6182036b

  20. ---> d0eb4627e01f

  21. Step 5/12 : RUN node clean.js

  22. ---> Running in f4d3c4745901

  23. Removing intermediate container f4d3c4745901

  24. ---> 602987ce7162

  25. Step 6/12 : RUN ./node_modules/mustache/bin/mustache events.json index.mustache > index.html

  26. ---> Running in 05b5ebd73b89

  27. Removing intermediate container 05b5ebd73b89

  28. ---> d982ff9cc61c

  29. Step 7/12 : FROM nginx:1.14.0

  30. ---> 86898218889a

  31. Step 8/12 : COPY --from=build /build/*.html /usr/share/nginx/html/

  32. ---> Using cache

  33. ---> e0c25127223f

  34. Step 9/12 : COPY events.json /usr/share/nginx/html/

  35. ---> Using cache

  36. ---> 64e8a1c5e79d

  37. Step 10/12 : COPY css /usr/share/nginx/html/css

  38. ---> Using cache

  39. ---> e524c31b64c2

  40. Step 11/12 : COPY js /usr/share/nginx/html/js

  41. ---> Using cache

  42. ---> 1ef9dece9bb4

  43. Step 12/12 : COPY img /usr/share/nginx/html/img

  44. ---> e50bf7836d2f

  45. Successfully built e50bf7836d2f

  46. Successfully tagged registry.gitlab.com/lucj/sophia.events:latest

  47. => web site available on http://localhost:32768


我们可以使用上述输出的末尾提供的URL访问网站页面。 
640?wx_fmt=png
目标环境

640?wx_fmt=png


云厂商创建的一台虚拟机
或许你也注意到了,这个网站并不是那么关键(每天只有几十次访问),也因此它只需要跑在一台单个的虚拟机上即可。该虚拟机是由Exoscale[3],一个伟大的欧洲云厂商,它上面的Docker Machine创建出来的。
顺便一提,如果你想试试Exoscale的服务的话,知会我一声,我可以提供20欧元的优惠券。
以Swarm模式启动的Docker守护进程
在上面这台虚拟机上运行的Docker守护进程被配置成以Swarm模式运行,因此它支持使用Docker Swarm原生提供的stack,service,config以及secret等原语和它强大(且易于使用)的编排功能。
以docker stack形式运行的应用
下述文件内容里定义了一个包含网站素材的nginx web服务器作为一个服务(service)运行。
 
 
  1. version: "3.7"

  2. services:

  3.  www:

  4.    image: registry.gitlab.com/lucj/sophia.events

  5.    networks:

  6.      - proxy

  7.    deploy:

  8.      mode: replicated

  9.      replicas: 2

  10.      update_config:

  11.        parallelism: 1

  12.        delay: 10s

  13.      restart_policy:

  14.        condition: on-failure

  15. networks:

  16.  proxy:

  17.    external: true


这里有几处需要解释下:
  • 镜像存储在托管到gitlab.com的私有镜像仓库(这里没涉及到Docker Hub)。

  • 服务是以2个副本的形式运行在副本模式下,这也就意味着同一时间该服务会有两个正在运行中的任务/容器。Swarm的service会关联一个VIP(虚拟IP地址),这样一来目标是该服务的每个请求会在两个副本之间实现负载均衡。

  • 每次完成服务更新时(部署一个新版本的网站),其中一个副本会被更新,然后在10秒后更新第二个副本。这可以确保在更新期间整个网站仍然可用。我们也可以使用回滚策略,但是在这里没有必要。

  • 服务会被绑定到一个外部的代理网络,这样一来TLS termination(在Swarm里部署的,跑在另外一个服务里,但是超出本项目的范畴)可以发送请求给www服务。


要运行这个stack只需要执行如下命令:
 
 
  1. $ docker stack deploy -c sophia.yml sophia_events


统御一切的Portainer
Portainer[4]是一套很棒的Wbe UI工具,它可以很方便地管理Docker宿主机和Docker Swarm集群。下面是Portainer操作界面的一张截图,里面列出了Swarm集群里当前可用的stack。  640?wx_fmt=png
当前设定下有3个stack:
  • Portainer自己

  • 包含了跑着我们网站的服务的sophia_events

  • tls,TLS termination服务


如果列出跑在sophia_events stack里的www服务的明细的话,我们将可以看到 该服务的webhook已经处于激活状态。Portainer 1.19.2(迄今为止最新的版本)已经加入了这一功能的支持,它允许定义一个HTTP Post端点,可以在被调用后触发一次服务的更新。正如我们稍后将会看到的,GitLab runner会负责调用这个webhook。 
640?wx_fmt=png
备注:从屏幕截图中可以看到,笔者是通过localhost:8888这个地址访问Portainer的用户界面。由于笔者不想将Portainer实例对外暴露,因此是通过SSH隧道访问,该隧道可以通过如下命令开启:
 
 
  1. ssh -i ~/.docker/machine/machines/labs/id_rsa -NL 8888:localhost:9000 $USER@$HOST


这样一来,目标是本地机器上的8888端口的所有请求均会通过SSH转发到虚拟机上的9000端口上。9000端口是Portainer在虚拟机上运行时监听的端口,但是并未对外开放,因为它被Exoscale配置的一个安全组禁用了。
备注:在上述命令里,用来连接虚拟机的ssh key是在虚拟机创建时由Docker Machine生成的一个key。
GitLab runner
Gitlab的runner是一个负责执行定义在.gitlab-ci.yml文件里的一组action的进程。就我们这个项目来说,我们定义了一个我们自己的runner,它在虚拟机上以一个容器的形式运行。
第一步就是带上一堆参数来注册该runner。
 
 
  1. CONFIG_FOLDER=/tmp/gitlab-runner-config

  2. docker run rm -t -i \

  3. -v $CONFIG_FOLDER:/etc/gitlab-runner \

  4. gitlab/gitlab-runner register \

  5.   --non-interactive \

  6.   --executor "docker" \

  7.   —-docker-image docker:stable \

  8.   --url "https://gitlab.com/" \

  9.   —-registration-token "$PROJECT_TOKEN" \

  10.   —-description "Exoscale Docker Runner" \

  11.   --tag-list "docker" \

  12.   --run-untagged \

  13.   —-locked="false" \

  14.   --docker-privileged


在上述参数中,PROJECT_TOKEN可以在GitLab.com的项目页面上找到,并可以用来注册外部的runner。 
640?wx_fmt=png
用来注册一个新的runner的注册token。
一旦runner注册上了,我们需要启动它:
 
 
  1. CONFIG_FOLDER=/tmp/gitlab-runner-config

  2. docker run -d \

  3. --name gitlab-runner \

  4. —-restart always \

  5. -v $CONFIG_FOLDER:/etc/gitlab-runner \

  6. -v /var/run/docker.sock:/var/run/docker.sock \

  7. gitlab/gitlab-runner:latest


等到它注册上了而且启动起来了,该runner便会出现在GitLab.com上的项目页面里。 
640?wx_fmt=png
为此项目创建的runner。
每当有新的commit推送到仓库,此runner随后便会接收到一些要做的任务。它会按顺序执行.gitlab-ci.yml文件里定义好的测试、构建和部署几个阶段。
 
 
  1. variables:

  2.  CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH

  3.  DOCKER_HOST: tcp://docker:2375

  4. stages:

  5.  - test

  6.  - build

  7.  - deploy

  8. test:

  9.  stage: test

  10.  image: node:8.12.0-alpine

  11.  script:

  12.    - npm i

  13.    - npm test

  14. build:

  15.  stage: build

  16.  image: docker:stable

  17.  services:

  18.    - docker:dind

  19.  script:

  20.    - docker image build -t $CONTAINER_IMAGE:$CI_BUILD_REF -t $CONTAINER_IMAGE:latest .

  21.    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com

  22.    - docker image push $CONTAINER_IMAGE:latest

  23.    - docker image push $CONTAINER_IMAGE:$CI_BUILD_REF

  24.  only:

  25.    - master

  26. deploy:

  27.  stage: deploy

  28.  image: alpine

  29.  script:

  30.    - apk add --update curl

  31.    - curl -XPOST $WWW_WEBHOOK

  32.  only:

  33.    - master

  • 测试阶段(test stage)将会运行一些预备检查,确保events.json文件格式正确,并且这里没有遗漏镜像

  • 构建阶段(build stage)会做镜像的构建并将它推送到GitLab上的镜像仓库

  • 部署阶段(deploy stage)将会通过发送给Portainer的一个webhook触发一次服务的更新。WWW_WEBHOOK变量的定义可以在Gitlab.com上项目页面的CI/CD设置里找到。


640?wx_fmt=png
备注:
  • runner在Swarm上是以一个容器的形式运行。我们可以使用一个共享的runner,这是一些公用的runner,它们会在托管到GitLab的不同项目所需的任务之间分配时间。但是,由于runner需要访问Portainer的端点(用来发送webhook),也因为笔者不希望Portainer能够从外界访问到,将runner跑在集群里会更安全一些。

  • 再者,由于runner跑在一个容器里,为了能够通过Portainer暴露在宿主机上的9000端口连到Portainer,它会将webhook请求发送到Docker0桥接网络上的IP地址。也因此,webhook将遵循如下格式:http://172.17.0.1:9000/api[…]a7-4af2-a95b-b748d92f1b3b。


部署流程

640?wx_fmt=png


新版本的站点更新遵循如下流程: 
640?wx_fmt=png
  1. 一个开发者推送了一些变更到GitLab。这些变更基本上囊括了events.json文件里一个或多个新的事件加上一些额外赞助商的logo。

  2. Gitlab runner执行在.gitlab-ci.yml里定义好的一组action。

  3. Gitlab runner调用在Portainer中定义的webhook。

  4. 在接收到webhook后,Portainer将会部署新版本的www服务。它通过调用Docker Swarm的API实现这一点。Portainer可以通过在启动时绑定挂载的/var/run/docker.sock套接字来访问该API。

    如果你想知道更多此unix套接字用法的相关信息,也许你会对之前这篇文章《Docker Tips : about /var/run/docker.sock[5]》感兴趣。

  5. 随后,用户便能看到新版本的站点。


示例
让我们一起来修改代码里的一些内容随后提交/推送这些变更。
 
 
  1. $ git commit -m 'Fix image'


  2. $ git push origin master


如下截图展示了GitLab.com上的项目页面里的commit触发的流水线作业。 
640?wx_fmt=png
在Portainer一侧,它将会收到一个webhook请求,随后会执行一次服务的更新操作。这里可能看不太清,但是一个副本已经完成了更新,通过第二个副本可以访问站点。随后,几秒钟之后,第二个副本也更新完毕。  640?wx_fmt=png
小结
即便对于这样一个小项目,为它建立一套CI/CD流水线也是一个很好的练习,尤其是可以更加熟悉GitLab(这一直在笔者要学习的列表里面),它是一个非常出色而且专业的产品。这也是一次体验大家期待已久的Portainer的最新版本(1.19.2)推出的webhook功能的机会。此外,对于像这样的副项目,Docker Swarm的使用是无脑上手的,很酷而且易于使用......
相关链接:

  1. https://gitlab.com/lucj/sophia.events

  2. https://gitlab.com/lucj/sophia.events/blob/master/index.mustache

  3. http://exoscale.ch/

  4. https://portainer.io/

  5. https://medium.com/lucjuggery/about-var-run-docker-sock-3bfd276e12fd


原文链接:https://medium.com/lucjuggery/even-the-smallest-side-project-deserves-its-ci-cd-pipeline-281f80f39fdf


Kubernetes实战培训

640?


Kubernetes实战培训将于2019年3月8日在深圳开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习 本次培训包括:云原生介绍、微服务;Docker基础、Docker工作原理、镜像、网络、存储、数据卷、安全;Kubernetes架构、核心组件、常用对象、网络、存储、认证、服务发现、调度和服务质量保证、日志、监控、告警、Helm、实践案例等。
640?wx_fmt=png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值