在 CI/CD 流程中,妥善管理 Docker 映像檔版本及確保其安全性至關重要。本文將介紹如何利用 Jenkins Pipeline 和 Anchore Engine 建立一套自動化的 Docker 映像檔標記和安全掃描機制。透過 Git 提交 ID 或分支名稱作為標籤,可以清楚追蹤映像檔版本與程式碼的對應關係,方便後續佈署和問題追蹤。同時,整合 Anchore Engine 可以自動掃描映像檔中的潛在漏洞,提升軟體交付的安全性。此流程能有效幫助團隊管理不同環境的佈署版本,並及早發現安全風險,降低資安事件發生的機率。
正確標記 Docker 映像檔的最佳實踐
在佈署私有 Docker 登入檔後,我們將更新每個服務的 Jenkinsfile,以便在 CI 管道執行成功後將映像檔推播到遠端私有登入檔。
9.3 正確標記 Docker 映像檔
首先,在 Jenkinsfile 中新增一個 Push 階段,使用 withRegistry 區塊對登入檔 URL 進行身份驗證。然後,將變更儲存到 ~/.docker/config.json。最後,使用等於構建編號 ID 的標籤值推播映像檔。
def imageName = 'mlabouardy/movies-loader'
def registry = 'https://registry.slowcoder.com'
node('workers') {
stage('Checkout') {
checkout scm
}
stage('Unit Tests') {
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside {
sh 'python test_main.py'
}
}
stage('Build') {
docker.build(imageName)
}
stage('Push') {
docker.withRegistry(registry, 'registry') {
docker.image(imageName).push(env.BUILD_ID)
}
}
}
內容解密:
def imageName和def registry:定義映像檔名稱和登入檔的 URL。node('workers'):指定在workers節點上執行 Jenkins 任務。stage('Push'):定義推播映像檔到登入檔的階段。docker.withRegistry(registry, 'registry'):使用提供的登入檔 URL 和憑據進行身份驗證。docker.image(imageName).push(env.BUILD_ID):將映像檔推播到登入檔,並使用構建編號 ID 作為標籤。
使用 Git 提交 ID 標記映像檔
雖然可以使用 Jenkins 構建 ID 標記映像檔,但更好的選擇是使用 Git 提交 ID。我們將建立一個函式,使用 Git 命令列取得提交 ID 並傳回它:
def commitID() {
sh 'git rev-parse HEAD > .git/commitID'
def commitID = readFile('.git/commitID').trim()
sh 'rm .git/commitID'
commitID
}
內容解密:
sh 'git rev-parse HEAD > .git/commitID':使用 Git 命令取得當前提交的 ID,並將其儲存到.git/commitID檔案中。def commitID = readFile('.git/commitID').trim():讀取.git/commitID檔案的內容,並去除多餘的空白字元。sh 'rm .git/commitID':刪除.git/commitID檔案。commitID:傳回取得的提交 ID。
然後,更新 Push 階段以使用 commitID() 函式傳回的值標記映像檔:
stage('Push') {
docker.withRegistry(registry, 'registry') {
docker.image(imageName).push(commitID())
}
}
根據分支名稱推播映像檔
為了進一步改進,我們將根據分支名稱推播相同的映像檔,並使用不同的標籤。這將有助於我們在處理持續佈署和交付時,將特定的標籤分配給特定的環境。
stage('Push') {
docker.withRegistry(registry, 'registry') {
docker.image(imageName).push(commitID())
if (env.BRANCH_NAME == 'develop') {
docker.image(imageName).push('develop')
}
}
}
內容解密:
if (env.BRANCH_NAME == 'develop'):檢查當前分支名稱是否為develop。docker.image(imageName).push('develop'):如果是,則將映像檔推播到登入檔,並使用develop作為標籤。
使用 Amazon ECR 作為私有登入檔
如果使用 Amazon ECR 作為私有登入檔,則需要在推播映像檔之前使用 AWS CLI 對遠端儲存函式庫進行身份驗證。
def imageName = 'mlabouardy/movies-loader'
def registry = 'ACCOUNT_ID.dkr.ecr.eu-west-3.amazonaws.com'
def region = 'REGION'
node('workers') {
...
stage('Push') {
sh "aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${registry}/${imageName}"
docker.image(imageName).push(commitID())
if (env.BRANCH_NAME == 'develop') {
docker.image(imageName).push('develop')
}
}
}
內容解密:
sh "aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${registry}/${imageName}":使用 AWS CLI 取得登入密碼,並使用該密碼對 Docker 進行身份驗證。docker.image(imageName).push(commitID()):將映像檔推播到 ECR 儲存函式庫,並使用提交 ID 作為標籤。
在觸發 CI 管道之前,需要授予 Jenkins 工作節點對 ECR 儲存函式庫執行推播操作的許可權。因此,需要為 Jenkins 工作節點分配具有 AmazonEC2ContainerRegistryFullAccess 策略的 IAM 例項組態檔案。
在 CI 流程中建置 Docker 映像檔的安全掃描機制
在現代化的軟體開發流程中,持續整合(CI)與容器化技術的結合已成為主流趨勢。當我們在 CI 流程中建置 Docker 映像檔時,如何確保這些映像檔的安全性成為一個重要的課題。本篇文章將探討如何在 CI 流程中整合容器映像檔掃描工具,以提升整體系統的安全性。
使用 Anchore Engine 進行容器映像檔掃描
Anchore Engine 是一個開源專案,提供集中式的容器映像檔檢查、分析和認證服務。它可以作為獨立服務執行,也可以佈署為 Docker 容器。透過 Anchore Engine,我們可以對建置的 Docker 映像檔進行安全漏洞掃描,確保其符合安全規範。
佈署 Anchore Engine
首先,我們需要在一個具有 Docker Community Edition(CE)的 EC2 例項上安裝 Docker Compose。接著,執行以下命令佈署 Anchore Engine:
curl https://docs.anchore.com/current/docs/engine/quickstart/docker-compose.yaml > docker-compose.yaml
docker-compose up -d
佈署完成後,使用 docker-compose ps 命令驗證容器是否正常執行。同時,確保只允許來自 Jenkins 主節點安全群組 ID 的流量進入埠 8228(Anchore API)。
將 Anchore Engine 整合到 Jenkins CI 流程
為了將 Anchore Engine 與 Jenkins CI 流程整合,我們需要安裝 Anchore Container Image Scanner 外掛程式。在 Jenkins 主選單中,選擇 Manage Jenkins,然後進入 Manage Plugins 頁面並安裝該外掛程式。
設定 Anchore 外掛程式
安裝完成後,在 Configure System 設定頁面中,找到 Anchore Configuration 部分並設定 Anchore URL 與憑證(預設為 admin/foobar)。
在 Jenkinsfile 中加入映像檔掃描階段
接下來,在 Jenkinsfile 中新增一個名為 Analyze 的階段,用於呼叫 Anchore 外掛程式對 Docker 映像檔進行掃描。
stage('Analyze'){
def scannedImage = "${registry}/${imageName}:${commitID()} ${workspace}/Dockerfile"
writeFile file: 'images', text: scannedImage
anchore name: 'images'
}
設定私人 Docker 倉函式庫的存取許可權
由於 Anchore 需要存取私人 Docker 倉函式庫中的映像檔,因此我們需要為其設定存取憑證。對於使用 Amazon ECR 的情況,可以賦予 EC2 例項一個具有 AmazonEC2ContainerRegistryReadOnly 許可權的 IAM 例項設定檔。
anchore-cli --u admin --p foobar registry add ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com awsauto awsauto --registry-type=awsecr
檢視掃描結果與報告
當 CI 流程執行完畢後,Anchore 將會產生掃描報告。如果映像檔存在高嚴重性的漏洞,流程將會失敗。掃描結果將以 JSON 檔案形式儲存,並在 Jenkins 介面上顯示建置狀態(STOP、WARN 或 FAIL)。同時,系統會自動發布 HTML 報告,顯示詳細的漏洞資訊。
詳細解說:程式碼與組態的作用
docker-compose up -d:此命令用於在背景啟動由docker-compose.yaml定義的服務。這裡主要啟動了 Anchore Engine 的各個元件。anchore-cli registry add:此命令用於向 Anchore Engine 登入一個 Docker 倉函式庫。這裡使用的是 Amazon ECR,因此需要指定對應的認證方式。writeFile file: 'images', text: scannedImage:在 Jenkinsfile 中,這行程式碼建立了一個名為images的檔案,其中包含了待掃描的 Docker 映像檔資訊。anchore name: 'images':這行程式碼呼叫了 Anchore 外掛程式,並傳入剛才建立的images檔案作為引數,以啟動對應的映像檔掃描任務。
圖表說明
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 圖表說明
rectangle "建置 Docker 映像檔" as node1
rectangle "觸發 Anchore 掃描" as node2
rectangle "掃描結果" as node3
rectangle "產生 HTML 報告" as node4
node1 --> node2
node2 --> node3
node3 --> node4
@enduml
圖表翻譯: 此圖表展示了 Jenkins CI 流程與 Anchore Engine 的整合過程。首先,Jenkins 建置並推播 Docker 映像檔至倉函式庫。接著,觸發 Anchore 對該映像檔進行安全掃描。最後,將掃描結果與詳細報告呈現於 Jenkins 上,供開發者參考。
Jenkins宣告式Pipeline的實作與應用
在前面的章節中,我們使用根據Groovy語法的指令碼式Pipeline來定義專案的CI流程。本文將介紹如何使用宣告式Pipeline來實作相同的功能。宣告式Pipeline是一種簡化且友好的語法,不需要深入學習Groovy語言即可定義Pipeline。
從指令碼式Pipeline轉換到宣告式Pipeline
首先,讓我們來看一個指令碼式Pipeline的例子,如下所示:
node('workers'){
stage('Checkout'){
checkout scm
}
stage('Unit Tests'){
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside{
sh "python main_test.py"
}
}
stage('Build'){
docker.build(imageName)
}
stage('Push'){
docker.withRegistry(registry, 'registry') {
docker.image(imageName).push(commitID())
if (env.BRANCH_NAME == 'develop') {
docker.image(imageName).push('develop')
}
}
}
}
內容解密:
node('workers')指定了Pipeline的執行環境在標籤為workers的節點上。stage('Checkout')階段用於簽出程式碼函式庫中的程式碼。stage('Unit Tests')階段構建了一個用於執行單元測試的Docker映象,並在容器中執行測試。stage('Build')階段構建了應用程式的Docker映象。stage('Push')階段將構建好的映象推播到遠端倉函式庫。
要將上述指令碼式Pipeline轉換為宣告式Pipeline,需要以下步驟:
- 將
node('workers')替換為pipeline關鍵字,並在pipeline塊內定義agent部分以指定執行環境。 - 使用
stages部分包裝所有的stage塊。 - 在每個
stage內使用steps塊包裝具體的指令。
轉換後的宣告式Pipeline如下:
pipeline{
agent{
label 'workers'
}
stages{
stage('Checkout'){
steps{
checkout scm
}
}
stage('Unit Tests'){
steps{
script {
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside{
sh "python test_main.py"
}
}
}
}
stage('Build'){
steps{
script {
docker.build(imageName)
}
}
}
stage('Push'){
steps{
script {
docker.withRegistry(registry, 'registry') {
docker.image(imageName).push(commitID())
if (env.BRANCH_NAME == 'develop') {
docker.image(imageName).push('develop')
}
}
}
}
}
}
}
內容解密:
agent{ label 'workers' }指定了Pipeline的執行環境在標籤為workers的節點上。stages部分包含了Pipeline的所有階段。- 每個
stage內的steps部分定義了該階段需要執行的步驟。 - 使用
script塊來執行複雜的指令碼指令。
使用Docker作為執行環境
宣告式Pipeline還支援使用Docker作為執行環境,如下所示:
pipeline{
agent{
docker {
image 'python:3.7.3'
}
}
stages{
stage('Checkout'){
steps{
checkout scm
}
}
stage('Unit Tests'){
steps{
script {
sh 'python test_main.py'
}
}
}
}
}
內容解密:
agent{ docker { image 'python:3.7.3' } }指定了使用Python 3.7.3的Docker映象作為執行環境。- 在
stage('Unit Tests')階段,直接在Docker容器中執行Python測試。
管理Pull Requests
為了更好地管理程式碼變更,我們應該建立特性分支併發起Pull Request,以在合併程式碼前進行測試和審查。Jenkins可以與GitHub整合,實作對Pull Request的自動化測試和反饋。
具體步驟包括建立特性分支、修改程式碼、提交變更、推播分支到遠端倉函式庫、建立Pull Request等。