我在90年代后期写了我的第一个网站。然后使它们进入工作状态非常简单。在某些共享主机上有一台Apache服务器,您可以通过FTP登录该服务器,并在浏览器行中编写类似的内容ftp://ftp.example.com
。然后,必须输入名称和密码,然后将文件上传到服务器。有时候,一切都比现在容易。
在过去的二十年中,一切都发生了很大变化。场地变得越来越复杂,必须将它们组装起来才能投入生产。一个单一的服务器已成为在负载平衡器后面工作的多个服务器;版本控制系统的使用已变得司空见惯。
对于我的个人项目,我有一个特殊的配置。而且我知道我需要能够在生产环境中部署站点的能力,只需执行一个操作即可:将代码编写到master
GitHub上的分支。另外,我知道要确保我的小型Web应用程序的运行,我不想管理一个巨大的Kubernetes集群,也不想使用Docker Swarm技术,也不想维护一个带有Pod,代理和各种其他困难的服务器园区。为了实现尽可能简化工作的目标,我需要熟悉CI / CD。如果您有一个小型项目(在我们的案例中,我们正在谈论一个Node.js项目),并且您想了解如何自动执行此项目的部署,同时确保存储在存储库中的内容完全匹配什么对生产有效,我想您可能会对本文感兴趣。先决条件
希望本文的读者具有命令行区域和编写Bash脚本的基本知识。此外,他将需要Travis CI和Docker Hub帐户。目标
我不会说这篇文章可以无条件地称为“培训手册”。这是一份文档,其中我谈论了我学到的东西,并描述了在适合我的生产环境中测试和部署代码的过程,这些过程是一次自动执行。这就是我的工作流程的结局。除了发送到存储库任何分支的代码外,master
还将执行以下操作:- 在Travis CI上构建项目。
- 执行所有的单元,集成和端到端测试。
仅针对进入master
以下代码的代码:- 以上所有内容,再加上...
- 根据当前代码,设置和环境构建Docker映像。
- 将映像放置在Docker Hub上。
- 连接到生产服务器。
- 将映像从Docker Hub上载到服务器。
- 停止当前容器,然后基于新映像启动一个新容器。
如果您对Docker,映像和容器一无所知-不用担心。我将告诉大家有关此事的信息。什么是CI / CD?
CI / CD的缩写代表“持续集成/持续部署”-“持续集成/持续部署”。▍持续整合
持续集成是开发人员对项目源代码的主存储库(通常是分支master
)进行提交的过程。在这种情况下,可以通过执行自动测试来确保代码的质量。▍持续部署
持续部署是生产中频繁的代码自动部署。缩写CI / CD的第二部分有时公开为“连续交付”(“ continuous delivery”)。通常,这与“连续部署”相同,但是“连续交付”意味着需要在开始项目部署过程之前手动确认更改。开始工作
我掌握了所有这些的应用程序称为TakeNote。这是我正在从事的Web项目,旨在做笔记。最初,我尝试制作一个JAMStack项目,或者只是一个没有服务器的前端应用程序,以利用Netlify提供的标准托管和部署功能。随着应用程序复杂性的增加,我需要创建其服务器部分,这意味着我将必须制定自己的策略以实现项目的自动集成和自动部署。就我而言,该应用程序是一个运行在Node.js环境中的Express服务器,它服务于单页React应用程序并支持安全的服务器API。该体系结构遵循本完整堆栈身份验证指南中的策略。我咨询了一位自动化专家的朋友,并问他需要做些什么才能使其按我的方式工作。他给了我一个想法,本文的目标部分概述了自动化工作流程的外观。我为自己设定了这样的目标,这意味着我需要弄清楚如何使用Docker。码头工人
Docker是一种工具,由于采用了容器化技术,因此即使Docker平台本身在不同的环境中工作,也可以轻松地分发应用程序以及在同一环境中部署和启动它们。首先,我需要使用Docker命令行工具(CLI)。安装Docker 的说明并不是很清楚易懂,但是您可以从中了解到,要迈出第一步的安装,您需要下载Docker Desktop(适用于Mac或Windows)。Docker Hub与git存储库或npm注册表的GitHub大致相同用于JavaScript包。这是Docker映像的在线存储库。它连接到它的Docker Desktop。因此,为了开始使用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
我知道如何在本地运行生产应用程序。我有一个Webpack配置,旨在构建一个现成的React应用程序。接下来,我有一个命令,用于在port上启动基于Node.js的服务器5000
。看起来像这样:npm i #
npm run build # React-
npm run start # Node-
应当指出,我没有该材料的示例应用程序。但是在这里,对于实验来说,任何简单的Node应用程序都适用。为了使用该容器,您需要提供Docker指令。这是通过Dockerfile
位于项目根目录中的一个文件完成的。起初,此文件似乎很晦涩。但是它仅包含一些特殊命令,描述了与设置工作环境类似的内容。以下是其中一些命令: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工具。
- 设置生产服务器,以便它可以加载和运行我们的代码。
在我们的案例中,Travis CI被用作CI / CD解决方案。作为服务器-DitigalOcean。应该注意的是,您可以在这里使用其他服务组合。例如,可以使用CircleCI或Github Actions代替Travis CI。而不是DigitalOcean-AWS或Linode。我们决定使用Travis CI,并且在此服务中我已经配置了一些东西。因此,现在我将简要讨论如何准备工作。特拉维斯
Travis CI是用于测试和部署代码的工具。由于每个项目都是唯一的,所以我不想复杂地设置Travis CI,这不会带来太多好处。但是,我将告诉您有关决定使用Travis CI的基础知识。无论选择什么-Travis CI,CircleCI,Jenkins或其他任何东西,类似的配置方法将在各处使用。为了开始使用Travis CI,请访问项目网站并创建一个帐户。然后将Travis CI与您的GitHub帐户集成。在系统设置期间,您将需要指定要用于自动化的存储库并启用对其的访问。(我使用GitHub,但是我确定Travis CI可以与BitBucket,GitLab和其他类似服务集成)。每次使用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开发人员服务器(这是我的工作流程的一项功能)并运行测试。如果要在存储库上显示带有测试代码覆盖率信息的图标,可以在此处找到有关使用Jest,Travis CI和Coveralls收集和显示此信息的简要说明。所以这是文件的内容.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
是可以使用的特拉维斯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,因此使用doctl命令连接到服务器。使用Aws时,将使用实用程序aws
,依此类推。设置服务器并不是特别困难。因此,我根据基本图像设置了一个小滴。应当注意,我选择的系统需要一次性手动安装Docker和一次性手动启动Docker。我使用Ubuntu 18.04安装Docker,因此,如果您使用Ubuntu进行相同的操作,则可以按照此简单指南进行操作。我在这里不是在谈论服务的特定命令,因为在不同情况下,这方面可能会有很大的不同。我只会给出通过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== you@example.com" | 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服务器,因此为了使Nginx和“加密反向代理” 正常工作,我需要进行很多修改。我真的不希望手动进行所有这些SSL设置,因此我创建了一个负载均衡器并将其信息记录在DNS中。例如,对于DigitalOcean,在负载均衡器上创建一个自我更新的自签名证书是一个简单,免费和快速的过程。这种方法还有一个优势,如果有必要,它可以使在运行负载均衡器的各种服务器上配置SSL非常容易。这样一来,服务器本身根本就不会“思考” SSL,而是照常使用端口80
。因此,与其他SSL配置方法相比,在负载平衡器上设置SSL更加简单和方便。现在,您可以在服务器上关闭所有接受传入连接的端口-除了用于80
与负载平衡器通信的端口22
和SSH 的端口以外。结果,除这两个端口外,尝试直接访问任何端口上的服务器都将失败。摘要
在完成了本材料中描述的所有操作之后,我不再担心Docker平台或自动CI / CD链的概念。我能够建立一个持续的集成链,在此过程中,代码将在投入生产之前经过测试,并将代码自动部署到服务器中。对我而言,所有这一切仍然相对较新,并且我确信有多种方法可以改善自动化工作流程并使其更加高效。因此,如果您对此主题有任何想法,请告诉我。希望本文对您的事务有所帮助。我想相信,在阅读完所有内容后,您会学到很多东西,而我却学到了很多。PS在我们的市场上有一个形象Docker,一键安装。您可以在VPS上检查容器的操作。所有新客户均可免费享受3天的测试。亲爱的读者们!您是否在项目中使用CI / CD技术?