我在 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 鏈的概念都不再讓我害怕。 我能夠建立一個持續整合鏈,在此期間程式碼在投入生產之前進行測試,並且程式碼會自動部署在伺服器上。 這對我來說仍然相對較新,而且我確信有方法可以改善我的自動化工作流程並使其更有效率。 因此,如果您對此事有任何想法,請告訴我。
聚苯乙烯 在我們的
親愛的讀者! 您在專案中使用 CI/CD 技術嗎?
來源: www.habr.com