71

使用Docker和GitLab构建一个CI/CD Pipeline

 5 years ago
source link: http://dockone.io/article/8287?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【编者的话】本文原文链接 here 。本文主要讲述了如何在GitLab上使用Docker镜像构建一个CI/CD的Pipeline。

FvYf6jn.png!web

现如今持续集成(CI)和持续交付(CD)大家已经不陌生了,它们是为了辅助你的产品/工程项目能够更快、更容易地运行最新版本。在这篇文章中,我将讲述如何使用Docker镜像和GitLab的CI/CD工具构建一个pipeline,在一个VPS/KVM Linux服务器上进行部署。

前提要求

  • 对Linux、Docker以及CI/CD有基本的了解。
  • GitLab 帐号(免费计划即可)。
  • 一台具备SSH访问权限的Linux服务器(非root用户即可)。我使用的是带有 LAMP 技术栈的Ubuntu 16.04 LTS系统。
  • 装有SSH和 LFTP 的轻量级Docker镜像。

在开始之前,你需要确保:

  • 你已经登录GitLab
  • 你是某个project/repository的拥有者
  • 你能够在本地机器通过Git访问这个repo进行pull和push操作

我用的是 GitKraken ,一个Git GUI工具,能够较为方面的进行Git操作。

关于GitLab的CI/CD

GitLab提供了一种通过Docker和Shared Runners处理CI/CD pipeline的简单方法。每次运行pipeline时,GitLab都会创建一个独立的虚拟机并构建一个Docker镜像。Pipeline可以使用YAML配置文件进行配置,一个pipeline可以有多个job,但如果job太多,pipeline的运行时间就较长。我们肯定不希望这样,因为使用免费计划, 每月最多可以有2000分钟的构建时间

“GitLab.com上的 Shared Runners自动缩放模式 运行,由DigitalOcean提供支持。自动缩放意味着减少启动构建的等待时间,并为每个项目建立隔离虚拟机,从而最大限度地提高安全性。”

-- 来自GitLab文档中的描述

为GitLab的runner创建SSH密钥

【备注】:即使你的服务器上已有具备SSH访问方式,还是建议你为CI/CD创建一套新的密钥,同时为部署流程创建一个新的非root用户。

我们将在Docker容器中通过SSH连接我们的服务器,这就意味着我们不能输入用户密码(即非交互式登录),因此我们需要在本地计算机中创建 无密码 的SSH密钥对。通常我会创建一个 2048字节的RSA密钥 ,因为这足够安全。

$ ssh-keygen rsa -b 2048

输入以上命令,跟随创建步骤,如果对创建步骤有疑问,使用 man ssh-key 。记住不要为密钥对设置密码。创建完成后,我们需要把私钥导入我们的服务器:

$ ssh-copy-id -i /path/to/key user@host

现在你可以尝试通过以下命令连接:

$ ssh -i /path/to/key user@host

连接过程应该不会让你输入密码。这个私钥我们后面会使用到。

选择Dockerfile

我使用Docker Hub来存放我的定制化Dockerfile,这个Dockerfile将基于 Alpine 构建一个安装有OpenSSH和LFTP的 轻量级镜像(大约8Mb) 。在GitLab的CI/CD中我们需要使用这个镜像来运行pipeline的job和脚本,镜像越轻量意味着下载镜像的时间就越少。你可以用你自己的镜像或者用我的 Dockerfile

Pipleline的配置

在正式构建前,你需要在你repo的根目录创建一个".gitlab-ci.yml"文件。接下来我将解释我使用的配置文件,如果有兴趣,你可以先到 GitLab官网 阅读配置文件格式以及所有可以使用的配置项。

我的配置文件如下:

image: jimmyadaro/gitlab-ci-cd:latest

Deploy:

stage: deploy

only:

— ‘master’

when: manual

allow_failure: false

before_script:

#Create .ssh directory

— mkdir -p ~/.ssh

#Save the SSH private key

— echo “$SSH_PRIVATE_KEY” > ~/.ssh/id_rsa

— chmod 700 ~/.ssh

— chmod 600 ~/.ssh/id_rsa

— eval $(ssh-agent -s)

— ssh-add ~/.ssh/id_rsa

script:

#Backup everything in /var/www/html/

— ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa $USERNAME@$HOST “zip -q -r /var/backups/www/01-Deploy-$(date +%F_%H-%M-%S).zip /var/www/html/”

#Deploy new files to /var/www/html

— lftp -d -u $USERNAME, -e ‘set sftp:auto-confirm true; set sftp:connect-program “ssh -a -x -i ~/.ssh/id_rsa”; mirror -Rnev ./ /var/www/html — ignore-time — exclude-glob .git* — exclude .git/; exit’ sftp://$HOST

— rm -f ~/.ssh/id_rsa

— ‘echo Deploy done: $(date “+%F %H:%M:%S”)’

让我们逐行看看配置文件的每一步都在做什么。

image: jimmyadaro/gitlab-ci-cd:latest

这行将告诉runner从Docker Hub上拉取并运行最新版本的容器。你可以在这里设置你想要使用的镜像,但 别忘了给镜像安装OpenSSH和LFTP

Deploy:

这行设置了pipeline的job名字,创建一个job必须设置这行内容。

stage: deploy

这行设置了job的stage名字,如果你需要运行多个stage,例如“backup”、“build”、“deploy”等,stage名字将帮助你识别当前pipeline处于什么状态。由于我不需要其他stage,所以我只用了一个job,并且这个job只有一个stage。对于job和stage的名字可以任意设置,例如你的job可以叫“ASDF”,stage可以叫“GHJK”,不过如果你有多个stage,你肯定需要鉴别不同的stage,因此我建议还是规范化这些名字。

only:

— ‘master’

这行表示pipeline只有当你repo的 master 分支收到一个更新(例如git merge)时才会被触发。因此,我建议开发使用其他分支(例如 developmentwip 等),然后使用 master 分支作为“产品分支”。

when: manual

这行表示你需要进入你的project的CI/CD配置中手动触发整个部署流程。当然,这一步是可以跳过的,只是我更喜欢手动触发pipeline。如果去掉这行,你所选分支(本例中为master)的任何改动都会触发一次pipeline。

allow_failure: false

这行表示如果你的pipeline中有其他stage,当一个job中发生错误时,不允许继续执行剩余任务。这是一个可选配置。

before_script:

#Create .ssh directory

— mkdir -p ~/.ssh

#Save the SSH private key

— echo “$SSH_PRIVATE_KEY” > ~/.ssh/id_rsa

— chmod 700 ~/.ssh

— chmod 600 ~/.ssh/id_rsa

— eval $(ssh-agent -s)

— ssh-add ~/.ssh/id_rsa

before_script 单元设置的所有命令都会在执行主单元(main script)之前执行。如你所见,每行shell命令需要用短横线(“-“)指定。上面的命令将把我们刚刚生成的SSH私钥保存到容器默认的SSH路径下,这样我们就可以免密连接我们的服务器。

刚刚生成的私钥将作为Protected变量保存在我的project的CI/CD配置中,在GitLab的web UI上,点击Settings > CI/CD > Variables将看到这个变量。同样,我将服务器地址和部署使用的用户名(非root用户)也使用Protected变量保存。

QRZnyuu.png!web
script:

#Backup everything in /var/www/html/

— ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa $USERNAME@$HOST “zip -q -r /var/backups/www/01-Deploy-$(date +%F_%H-%M-%S).zip /var/www/html/”

#Deploy new files to /var/www/html

— lftp -d -u $USERNAME, -e ‘set sftp:auto-confirm true; set sftp:connect-program “ssh -a -x -i ~/.ssh/id_rsa”; mirror -Rnev ./ /var/www/html — ignore-time — exclude-glob .git* — exclude .git/; exit’ sftp://$HOST

— rm -f ~/.ssh/id_rsa

— ‘echo Deploy done: $(date “+%F %H:%M:%S”)’

script下的内容就是GitLab的runner执行的主单元。首先,我会连接到我的服务器将所有内容备份到一个ZIP文件中,这个ZIP文件将使用当前时间(格式为yyyy-mm-dd_hh-mm-ss)进行命名:

— ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa $USERNAME@$HOST “zip -q -r /var/backups/www/01-Deploy-$(date +%F_%H-%M-%S).zip /var/www/html/”

【备注】:你需要在你的服务器上安装ZIP CLI。

在将 /var/www/html 备份后,使用LFTP连接到我的服务器并且上传最新的repo文件。这里我用的是SFTP,FTP配置有点不一样:

— lftp -d -u $USERNAME, -e ‘set sftp:auto-confirm true; set sftp:connect-program “ssh -a -x -i ~/.ssh/id_rsa”; mirror -Rnev ./ /var/www/html — ignore-time — exclude-glob .git* — exclude .git/; exit’ sftp://$HOST

使用 mirror -Rnev ./ /var/www/html 让LFTP上传 ./ (我repo的根目录)下的所有文件到我服务器的 /var/www/html 路径下。上面部分参数的意思如下:

  • -u 设置了我们 sftp://$HOST 的SSH用户名。
  • -e 用于设置执行命令(使用单引号进行配置)。
  • -R 用于设置reverse mirror。
  • -n 表示只上传新的文件。
  • -e 用于删除在我们源中不存在的文件。
  • -v 用于配置verbose日志。
  • ignore-time 将在决定是否下载时忽略时间。
  • exclude-glob .git* 将会排除任何目录中匹配 .git* 的所有文件(例如 .gitignore 以及 .gitkeep )。你可以在这里设置其他文件匹配方式。
  • exclude .git/ 这个配置将会保证不上传我们repo中的git文件。
  • exit 将会停止LFTP和SSH执行。

【备注】:所有在我们服务上但是不在我们repository中的文件将被删除,记住上面所述的'源'指的就是我们GitLab的repository。

最终,脚本会在shared runner的容器中删除我们的私钥(这是一个安全措施),并且输出带有当前时间的结束语句。

— rm -f ~/.ssh/id_rsa

— ‘echo Deploy done: $(date “+%F %H:%M:%S”)’

以上部分就是我配置文件的所有内容。在GitLab中一个成功的pipeline执行流程如下图所示:

FBBVV3y.png!web

运行Docker镜像

RRVVbaF.png!web

pipeline的最终状态

结论

我尝试了一些其他的方式,例如使用rsync替代LFTP、使用多阶段以及缓存依赖(我能够重用SSH密钥)的Jobs、使用Docker的ENTRYPOINT和CMD等等,但我发现上面描述的方式对我来说是最快和最容易的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK