我在 90 年代末写了第一个网站。 那时,让它们投入工作是非常容易的。 某些共享主机上有一个 Apache 服务器,您可以通过编写以下内容通过 FTP 登录到该服务器 ftp://ftp.example.com
。 然后您必须输入您的名称和密码并将文件上传到服务器。 时代不同了,那时的一切都比现在简单。
在那之后的二十年里,一切都发生了很大的变化。 网站变得更加复杂;在发布到生产环境之前必须对其进行组装。 一台服务器变成了在负载平衡器后面运行的许多服务器,并且版本控制系统的使用变得司空见惯。
对于我的个人项目,我有一个特殊的配置。 我知道我需要能够通过执行一个操作来在生产中部署站点:将代码写入分支 master
在 GitHub 上。 另外,我知道为了保证我的小型 Web 应用程序的运行,我不想管理一个巨大的 Kubernetes 集群,或者使用 Docker Swarm 技术,或者维护一组带有 Pod、代理和各种其他的服务器。复杂性。 为了实现让工作尽可能简单的目标,我需要熟悉 CI/CD。
如果您有一个小项目(在本例中为 Node.js 项目),并且您想知道如何自动部署该项目,同时确保存储库中存储的内容与生产中的内容完全匹配,那么我我认为您可能对这篇文章感兴趣。
先决条件
本文的读者应该对命令行和编写 Bash 脚本有基本的了解。 此外,他还需要账户
目标
我不会说这篇文章可以无条件地称为“教程”。 这更像是一份文档,其中我谈论了我所学到的知识,并描述了适合我在一次自动传递中执行的测试和部署代码到生产的过程。
这就是我的工作流程最终的样子。
对于发布到任何存储库分支的代码,除了 master
,执行以下操作:
- 基于 Travis CI 的项目构建启动。
- 执行所有单元、集成和端到端测试。
仅适用于属于以下情况的代码 master
,执行以下操作:
- 上面提到的一切,加上...
- 根据当前代码、设置和环境构建 Docker 映像。
- 将镜像部署到 Docker Hub。
- 连接到生产服务器。
- 将镜像从 Docker Hub 上传到服务器。
- 停止当前容器并基于新镜像启动一个新容器。
如果您对 Docker、镜像和容器一无所知,也不必担心。 我会告诉你一切。
什么是 CI/CD?
CI/CD 缩写代表“持续集成/持续部署”。
▍持续集成
持续集成是开发人员向项目的主源代码存储库(通常是分支 master
)。 同时,通过自动化测试保证代码的质量。
▍持续部署
持续部署是将代码频繁、自动地部署到生产中。 CI/CD 缩写词的第二部分有时被拼写为“持续交付”。 这与“持续部署”基本相同,但“持续交付”意味着在开始项目部署过程之前需要手动确认更改。
入门
我用来学习这一切的应用程序叫做
就我而言,该应用程序是在 Node.js 环境中运行的 Express 服务器,为单页 React 应用程序提供服务并支持安全的服务器端 API。 该架构遵循以下策略:
我咨询过
码头工人
Docker 是一种工具,得益于容器化技术,即使 Docker 平台本身运行在不同的环境中,应用程序也可以在同一环境中轻松分发、部署和运行。 首先,我需要掌握 Docker 命令行工具 (CLI)。
Docker Hub 与以下内容大致相同
因此,为了开始使用 Docker,您需要做两件事:
之后,您可以通过运行以下命令检查 Docker 版本来检查 Docker CLI 是否正常工作:
docker -v
接下来,在询问时输入您的用户名和密码,登录 Docker Hub:
docker login
要使用Docker,您必须了解镜像和容器的概念。
▍图片
图像类似于蓝图,其中包含组装容器的说明。 这是应用程序文件系统和设置的不可变快照。 开发者可以轻松共享图像。
# Вывод сведений обо всех образах
docker images
该命令将输出一个具有以下标题的表:
REPOSITORY TAG IMAGE ID CREATED SIZE
---
接下来我们将看一些相同格式的命令示例 - 首先是一个带有注释的命令,然后是它可以输出的示例。
▍集装箱
容器是一个可执行包,其中包含运行应用程序所需的所有内容。 无论基础设施如何,采用这种方法的应用程序将始终以相同的方式工作:在隔离环境中和在同一环境中。 关键是同一镜像的实例在不同的环境中启动。
# Перечисление всех контейнеров
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
---
▍标签
标签是图像的特定版本的指示。
▍Docker命令快速参考
以下是一些常用 Docker 命令的概述。
团队
上下文
行动
图片
从 Dockerfile 构建镜像
图片
图像标记
图片
列表图片
容器
基于镜像运行容器
图片
将图像上传到注册表
图片
从注册表加载图像
容器
列出容器
图像/容器
删除未使用的容器和镜像
▍Dockerfile
我知道如何在本地运行生产应用程序。 我有一个 Webpack 配置,旨在构建现成的 React 应用程序。 接下来,我有一个命令在端口上启动基于 Node.js 的服务器 5000
. 它看起来像这样:
npm i # установка зависимостей
npm run build # сборка React-приложения
npm run start # запуск Node-сервера
应该指出的是,我没有该材料的示例应用程序。 但在这里,对于实验来说,任何简单的 Node 应用程序都可以。
为了使用容器,您需要向 Docker 发出指令。 这是通过一个名为的文件完成的 Dockerfile
,位于项目的根目录下。 乍一看,这个文件似乎非常难以理解。
但它包含的内容只是通过特殊命令描述了类似于设置工作环境的内容。 以下是其中一些命令:
从 — 此命令启动一个文件。 它指定构建容器的基础映像。COPY — 将文件从本地源复制到容器。工作目录 — 设置以下命令的工作目录。跑 - 运行命令。暴露 — 端口设置。入口点 — 指示要执行的命令。
Dockerfile
可能看起来像这样:
# Загрузить базовый образ
FROM node:12-alpine
# Скопировать файлы из текущей директории в директорию app/
COPY . app/
# Использовать app/ в роли рабочей директории
WORKDIR app/
# Установить зависимости (команда npm ci похожа npm i, но используется для автоматизированных сборок)
RUN npm ci --only-production
# Собрать клиентское React-приложение для продакшна
RUN npm run build
# Прослушивать указанный порт
EXPOSE 5000
# Запустить Node-сервер
ENTRYPOINT npm run start
根据您选择的基础映像,您可能需要安装其他依赖项。 事实上,一些基础镜像(如 Node Alpine Linux)的创建目的是使它们尽可能紧凑。 因此,他们可能没有您期望的一些程序。
▍构建、标记和运行容器
容器的本地组装和启动是在我们完成之后 Dockerfile
,任务非常简单。 在将镜像推送到 Docker Hub 之前,您需要在本地进行测试。
▍组装
首先你需要收集 latest
).
# Сборка образа
docker build -t <image>:<tag> .
运行此命令后,您可以观看 Docker 构建镜像。
Sending build context to Docker daemon 2.88MB
Step 1/9 : FROM node:12-alpine
---> ...выполнение этапов сборки...
Successfully built 123456789123
Successfully tagged <image>:<tag>
构建可能需要几分钟 - 这完全取决于您有多少依赖项。 构建完成后,可以运行命令 docker images
并查看新图像的描述。
REPOSITORY TAG IMAGE ID CREATED SIZE
<image> latest 123456789123 About a minute ago x.xxGB
▍启动
图像已创建。 这意味着您可以基于它运行容器。 因为我希望能够访问容器中运行的应用程序 localhost:5000
,我,在这对人的左边 5000:5000
在下一个命令中安装 5000
。 右侧是集装箱码头。
# Запуск с использованием локального порта 5000 и порта контейнера 5000
docker run -p 5000:5000 <image>:<tag>
现在容器已创建并运行,您可以使用命令 docker ps
查看有关此容器的信息(或者您可以使用命令 docker ps -a
,它显示有关所有容器的信息,而不仅仅是正在运行的容器)。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
987654321234 <image> "/bin/sh -c 'npm run…" 6 seconds ago Up 6 seconds 0.0.0.0:5000->5000/tcp stoic_darwin
如果您现在转到该地址 localhost:5000
— 您可以看到正在运行的应用程序的页面与在生产环境中运行的应用程序的页面完全相同。
▍标签与发布
为了在生产服务器上使用创建的镜像之一,我们需要能够从 Docker Hub 下载该镜像。 这意味着您首先需要在 Docker Hub 上为该项目创建一个存储库。 之后,我们将有一个可以发送图像的地方。 该镜像需要重命名,使其名称以我们的 Docker Hub 用户名开头。 后面应该跟存储库的名称。 任何标签都可以放置在名称末尾。 下面是使用此方案命名图像的示例。
现在您可以使用新名称构建映像并运行命令 docker push
将其推送到 Docker Hub 存储库。
docker build -t <username>/<repository>:<tag> .
docker tag <username>/<repository>:<tag> <username>/<repository>:latest
docker push <username>/<repository>:<tag>
# На практике это может выглядеть, например, так:
docker build -t user/app:v1.0.0 .
docker tag user/app:v1.0.0 user/app:latest
docker push user/app:v1.0.0
如果一切顺利,该镜像将在 Docker Hub 上可用,并且可以轻松上传到服务器或传输给其他开发人员。
下一步
到目前为止,我们已经验证了该应用程序以 Docker 容器的形式在本地运行。 我们已将容器上传到 Docker Hub。 所有这些都意味着我们已经朝着我们的目标取得了很好的进展。 现在我们还需要解决两个问题:
- 设置用于测试和部署代码的 CI 工具。
- 设置生产服务器,以便它可以下载并运行我们的代码。
在我们的例子中,我们使用
应该注意的是,在这里您可以使用其他服务组合。 例如,您可以使用 CircleCI 或 Github Actions 来代替 Travis CI。 而不是 DigitalOcean - AWS 或 Linode。
我们决定与 Travis CI 合作,并且我已经在该服务中配置了一些东西。 所以,现在我简单讲一下如何做好工作准备。
特拉维斯CI
Travis CI 是一个用于测试和部署代码的工具。 我不想深入讨论设置 Travis CI 的复杂性,因为每个项目都是独一无二的,这不会带来太多好处。 但如果您决定使用 Travis CI,我将介绍基础知识以帮助您入门。 无论你选择 Travis CI、CircleCI、Jenkins 还是其他东西,到处都会使用类似的配置方法。
要开始使用 Travis CI,请访问
每次启动 Travis CI 时,都会启动服务器,执行配置文件中指定的命令,包括部署相应的存储库分支。
▍作业生命周期
Travis CI 配置文件名为 .travis.yml
并保存在项目根目录下,支持事件的概念
apt addons
cache components
before_install
install
before_script
script
before_cache
after_success или after_failure
before_deploy
deploy
after_deploy
after_script
▍测试
在配置文件中,我将配置本地 Travis CI 服务器。 我选择 Node 12 作为语言,并告诉系统安装使用 Docker 所需的依赖项。
列出的所有内容 .travis.yml
,除非另有指定,否则将在向存储库的所有分支发出所有拉取请求时执行。 这是一个有用的功能,因为它意味着我们可以测试进入存储库的所有代码。 这可以让您知道代码是否已准备好写入分支。 master
,以及是否会破坏项目构建过程。 在此全局配置中,我在本地安装所有内容,在后台运行 Webpack 开发服务器(这是我的工作流程的一个功能),并运行测试。
如果您希望存储库显示指示测试覆盖率的徽章,
所以这是文件的内容 .travis.yml
:
# Установить язык
language: node_js
# Установить версию Node.js
node_js:
- '12'
services:
# Использовать командную строку Docker
- docker
install:
# Установить зависимости для тестов
- npm ci
before_script:
# Запустить сервер и клиент для тестов
- npm run dev &
script:
# Запустить тесты
- npm run test
这是对存储库的所有分支和拉取请求执行的操作结束的地方。
▍部署
基于所有自动化测试成功完成的假设,我们可以(可选)将代码部署到生产服务器。 因为我们只想对分支中的代码执行此操作 master
,我们在部署设置中给系统适当的指令。 在您尝试使用我们接下来将在您的项目中查看的代码之前,我想警告您,您必须有一个用于部署的实际脚本。
deploy:
# Собрать Docker-контейнер и отправить его на Docker Hub
provider: script
script: bash deploy.sh
on:
branch: master
部署脚本解决了两个问题:
- 使用 CI 工具(在我们的例子中为 Travis CI)构建、标记镜像并将其发送到 Docker Hub。
- 将图像加载到服务器上,停止旧容器并启动新容器(在我们的示例中,服务器在 DigitalOcean 平台上运行)。
首先,您需要设置一个自动流程来构建、标记镜像并将其推送到 Docker Hub。 这与我们已经手动完成的操作非常相似,只是我们需要一种为图像分配唯一标签并自动登录的策略。 我对部署脚本的一些细节感到困难,例如标记策略、登录、SSH 密钥编码、SSH 连接建立。 但幸运的是,我的男朋友非常擅长 bash,就像其他许多事情一样。 他帮我写了这个剧本。
因此,脚本的第一部分是将映像上传到 Docker Hub。 这很容易做到。 我使用的标记方案涉及组合 git 哈希和 git 标签(如果存在)。 这确保了标签是唯一的,并且更容易识别它所基于的程序集。 DOCKER_USERNAME
и DOCKER_PASSWORD
是可以使用 Travis CI 界面设置的用户环境变量。 Travis CI 将自动处理敏感数据,以免其落入坏人之手。
这是脚本的第一部分 deploy.sh
.
#!/bin/sh
set -e # Остановить скрипт при наличии ошибок
IMAGE="<username>/<repository>" # Образ Docker
GIT_VERSION=$(git describe --always --abbrev --tags --long) # Git-хэш и теги
# Сборка и тегирование образа
docker build -t ${IMAGE}:${GIT_VERSION} .
docker tag ${IMAGE}:${GIT_VERSION} ${IMAGE}:latest
# Вход в Docker Hub и выгрузка образа
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
docker push ${IMAGE}:${GIT_VERSION}
脚本的第二部分的内容完全取决于您使用的主机以及与其连接的组织方式。 就我而言,由于我使用 Digital Ocean,因此我使用命令连接到服务器 aws
等
设置服务器并不是特别困难。 因此,我根据基础图像设置了一个水滴。 需要注意的是,我选择的系统需要一次性手动安装Docker并一次性手动启动Docker。 我使用Ubuntu 18.04安装Docker,所以如果你也使用Ubuntu来安装Docker,你可以按照
我在这里不是谈论该服务的具体命令,因为这方面在不同情况下可能会有很大差异。 我将给出通过 SSH 连接到将部署项目的服务器后要执行的一般操作计划:
- 我们需要找到当前正在运行的容器并停止它。
- 然后您需要在后台启动一个新容器。
- 您需要将服务器的本地端口设置为
80
- 这将允许您通过以下地址进入网站example.com
,不指定端口,而不是使用类似的地址example.com:5000
. - 最后,您需要删除所有旧的容器和映像。
这是脚本的延续。
# Найти ID работающего контейнера
CONTAINER_ID=$(docker ps | grep takenote | cut -d" " -f1)
# Остановить старый контейнер, запустить новый, очистить систему
docker stop ${CONTAINER_ID}
docker run --restart unless-stopped -d -p 80:5000 ${IMAGE}:${GIT_VERSION}
docker system prune -a -f
一些需要注意的事项
当您从 Travis CI 通过 SSH 连接到服务器时,您可能会看到一条警告,该警告将阻止您继续安装,因为系统将等待用户的响应。
The authenticity of host '<hostname> (<IP address>)' can't be established.
RSA key fingerprint is <key fingerprint>.
Are you sure you want to continue connecting (yes/no)?
我了解到可以用 Base64 对字符串密钥进行编码,以便将其保存为可以方便可靠地使用的形式。 在安装阶段,您可以解码公钥并将其写入文件 known_hosts
为了摆脱上述错误。
echo <public key> | base64 # выводит <публичный ключ, закодированный в base64>
实际上,该命令可能如下所示:
echo "123.45.67.89 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== [email protected]" | base64
下面是它生成的内容 - Base64 编码的字符串:
MTIzLjQ1LjY3Ljg5IHNzaC1yc2EgQUFBQUIzTnphQzF5YzJFQUFBQUJJd0FBQVFFQWtsT1Vwa0RIcmZIWTE3U2JybVRJcE5MVEdLOVRqb20vQldEU1UKR1BsK25hZnpsSERUWVc3aGRJNHlaNWV3MThKSDRKVzlqYmhVRnJ2aVF6TTd4bEVMRVZmNGg5bEZYNVFWa2JQcHBTd2cwY2RhMwpQYnY3a09kSi9NVHlCbFdYRkNSK0hBbzNGWFJpdEJxeGlYMW5LaFhwSEFac01jaUxxOFY2UmpzTkFRd2RzZE1GdlNsVksvN1hBCnQzRmFvSm9Bc25jTTFROXg1KzNWMFd3NjgvZUlGbWIxenVVRmxqUUpLcHJyWDg4WHlwTkR2allOYnk2dncvUGIwcndlcnQvRW4KbVorQVc0T1pQblRQSTg5WlBtVk1MdWF5ckQyY0U4NlovaWw4YitndzNyMysxbkthdG1Ja2puMnNvMWQwMVFyYVRsTXFWU3NieApOclJGaTl3cmYrTTdRPT0geW91QGV4YW1wbGUuY29tCg==
这是上面提到的命令
install:
- echo < публичный ключ, закодированный в base64> | base64 -d >> $HOME/.ssh/known_hosts
建立连接时可以对私钥使用相同的方法,因为您很可能需要私钥来访问服务器。 使用密钥时,您只需确保它安全地存储在 Travis CI 环境变量中并且不会显示在任何地方。
另一件需要注意的事情是,您可能需要将整个部署脚本作为一行运行,例如 - 使用 doctl
。 这可能需要一些额外的努力。
doctl compute ssh <droplet> --ssh-command "все команды будут здесь && здесь"
TLS/SSL 和负载平衡
完成上述所有操作后,我遇到的最后一个问题是服务器没有 SSL。 由于我使用 Node.js 服务器,为了强制
我真的不想手动完成所有这些 SSL 配置,因此我只是创建了一个负载均衡器并将其详细信息记录在 DNS 中。 以 DigitalOcean 为例,在负载均衡器上创建自动更新的自签名证书是一个简单、免费且快速的过程。 此方法还有一个额外的好处,即如果需要,可以非常轻松地在负载均衡器后面运行的多个服务器上设置 SSL。 这允许服务器本身根本不“考虑”SSL,但同时照常使用端口 80
。 因此,在负载均衡器上设置 SSL 比设置 SSL 的其他方法更容易、更方便。
现在您可以关闭服务器上接受传入连接的所有端口 - 除了端口 80
,用于与负载均衡器通信,端口 22
用于 SSH。 因此,尝试通过这两个端口以外的任何端口直接访问服务器都将失败。
结果
在我完成了本材料中谈到的所有内容之后,Docker 平台和自动化 CI/CD 链的概念都不再让我害怕。 我能够建立一个持续集成链,在此期间代码在投入生产之前进行测试,并且代码会自动部署在服务器上。 这对我来说仍然相对较新,而且我确信有方法可以改进我的自动化工作流程并使其更加高效。 因此,如果您对此事有任何想法,请告诉我。
PS 在我们的
亲爱的读者! 您在项目中使用 CI/CD 技术吗?
来源: habr.com