在建構雲原生應用程式時,CI/CD 生產線扮演著至關重要的角色。本文將分享我在 Jenkins CI/CD 生產線中整合 Docker、Kubernetes 和 Terraform 的實務經驗,涵蓋 Docker 映像檔的儲存、標記策略、私有登入檔設定以及漏洞掃描等關鍵環節。

Docker 映像檔的儲存與管理

頻繁的建置和套件釋出是持續整合的常態,因此,有效管理這些二進位程式碼至關重要。傳統的版本控制系統(如 Git 和 SVN)主要用於儲存程式碼,而二進位檔案則需要專用的儲存函式庫工具。

我個人推薦使用二進位儲存函式庫工具,例如 Nexus 或 Artifactory。然而,管理和強化這些執行個體也帶來一定的挑戰。幸運的是,雲端供應商提供了受管理的解決方案,例如 Amazon ECR、Google Container Registry 和 Azure Container Registry,簡化了管理流程。當然,您也可以選擇在 DockerHub 上託管 Docker 映像檔。

Nexus Repository OSS 的應用程式

Nexus Repository OSS 是一款我經常使用的開放原始碼免費成品儲存函式庫,支援儲存各種二進位檔案和建置成品,套件括 Maven/Java、npm、Helm 和 Docker 等。

由於我們主要使用 Docker,因此可以利用 Sonatype 提供的 Docker 映像檔在 Docker 容器中執行 Nexus Repository OSS。

佈署 Nexus Repository OSS 的步驟如下:

  1. 使用 Packer 烘焙新的機器映像檔,其中包含 Nexus Repository OSS 和必要的設定。
  2. 使用 Terraform 佈建 EC2 執行個體,並以烘焙好的 Nexus OSS AMI 作為基礎映像檔。
  3. 佈建公用負載平衡器,將 HTTP 和 HTTPS 流量轉發至 Nexus Repository Manager 的連線埠 8081。
  4. 設定 Docker 私有登入檔,指向 Nexus Repository Manager 上託管儲存函式庫的連線埠 5000。

完成佈署後,您可以透過瀏覽器存取 Nexus 儀錶板,並設定 Docker 託管儲存函式庫。我建議停用標籤不變性,允許使用相同標籤的後續映像檔推播覆寫映像檔標籤。

為了確保安全性,需要建立自訂 Nexus 角色,授予對 Docker 託管儲存函式庫的完整存取許可權。接著,建立 Jenkins 使用者,並將自訂 Nexus 角色指派給它。最後,在 Jenkins 上設定對應的登入檔憑證。

Amazon ECR 的應用程式

如果您使用 AWS,Amazon ECR 是另一個不錯的選擇。它是一項完全託管的 Docker 容器登入檔,讓您可以輕鬆儲存、管理和佈署 Docker 映像檔。使用 Amazon ECR,您無需管理自己的登入檔基礎設施,並且可以受益於 AWS 的安全性、可擴充功能性和可靠性。

Docker 映像檔標記策略

設定 Docker 私有登入檔後,接下來需要制定合理的映像檔標記策略。我通常使用建置 ID、Git 遞交 ID 和分支名稱進行標記。

以下是一個 Jenkinsfile 的範例,展示瞭如何使用建置 ID 作為標籤推播 Docker 映像檔:

def imageName = 'my-app'
def registry = 'my-registry.com'
node('workers') {
  stage('Build') {
    docker.build(imageName)
  }
  stage('Push') {
    docker.withRegistry(registry, 'registry-credentials') {
      docker.image(imageName).push(env.BUILD_ID)
    }
  }
}
  • docker.withRegistry() 使用指定的憑證向 Docker 登入檔進行驗證。
  • docker.image(imageName).push(env.BUILD_ID) 將映像檔推播到登入檔,並使用建置 ID 作為標籤。

雖然建置 ID 可用,但我更傾向於使用 Git 遞交 ID 作為標籤,因為它更具可讀性和可追溯性。以下是一個擷取 Git 遞交 ID 的函式:

def commitID() {
  sh 'git rev-parse HEAD > .git/commitID'
  def commitID = readFile('.git/commitID').trim()
  sh 'rm .git/commitID'
  return commitID
}

然後,您可以更新推播階段,使用 commitID() 函式傳回的值作為標籤:

stage('Push') {
    docker.withRegistry(registry, 'registry-credentials') {
      docker.image(imageName).push(commitID())
    }
}

此外,我還會使用分支名稱作為標籤,以便區分不同環境的映像檔,例如 latestpreproddevelop

stage('Push') {
  docker.withRegistry(registry, 'registry-credentials') {
    docker.image(imageName).push(commitID())
    if (env.BRANCH_NAME == 'develop') {
      docker.image(imageName).push('develop')
    }
  }
}

Docker 映像檔漏洞掃描

為了確保應用程式的安全性,在 CI/CD 生產線中整合漏洞掃描工具至關重要。我推薦使用 Anchore Engine,它是一個開放原始碼的容器映像檔檢查、分析和認證平台。

您可以選擇獨立安裝 Anchore Engine,或者更簡便地使用 Docker Compose 進行佈署。

docker-compose up -d

設定 Anchore Engine 後,您可以在 Jenkins 中設定對應的憑證,並在生產線中加入掃描步驟。

在本文中,我分享了在 Jenkins CI/CD 生產線中整合 Docker、Kubernetes 和 Terraform 的一些實務經驗,涵蓋了 Docker 映像檔的儲存、標記策略、私有登入檔設定以及漏洞掃描等關鍵環節。希望這些經驗能幫助您構建更安全、更高效的雲原生應用程式。

{
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "ap-southeast-1",
      "source_ami_filter": {
        "filters": {
          "virtualization-type": "hvm",
          "name": "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*",
          "root-device-type": "ebs"
        },
        "owners": ["099720109477"],
        "most_recent": true
      },
      "instance_type": "t2.micro",
      "ssh_username": "ubuntu",
      "ami_name": "packer-docker-{{timestamp}}"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sleep 30",
        "sudo apt-get update",
        "sudo apt-get -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common",
        "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -",
        "sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"",
        "sudo apt-get update",
        "sudo apt-get -y install docker-ce docker-ce-cli containerd.io",
        "sudo usermod -aG docker ubuntu",
        "sudo systemctl enable docker",
        "sudo curl -L \"https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose",
        "sudo chmod +x /usr/local/bin/docker-compose"
      ]
    }
  ]
}

這個 JSON 設定檔定義了 Packer 的建置流程。它使用 amazon-ebs 建置器,根據 Ubuntu 18.04 AMI 建立一個 AWS EBS 支援的映像檔。它會篩選最新的 HVM SSD Ubuntu 映像檔,並使用 t2.micro 執行個體型別。設定檔中指定了 SSH 使用者名稱 ubuntu 和 AMI 名稱 packer-docker-{{timestamp}}provisioners 區段包含一系列 shell 指令,用於安裝 Docker Engine、Docker Compose 和其他必要的套件。它首先更新 apt 套件,然後安裝必要的相依性。接著,它新增 Docker 的 GPG 金鑰和儲存函式庫,安裝 Docker CE,並將 ubuntu 使用者新增至 docker 群組。最後,它啟用 Docker 服務,並安裝 Docker Compose。

使用以下命令執行 Packer 建置:

packer build template.json

建置完成後,您將在 AWS 主控台中找到新的 AMI。記下 AMI ID,因為我們將在 Terraform 設定中使用它。

在現代軟體開發中,容器化和叢集管理技術至關重要。Docker Swarm 作為一個原生 Docker 叢集解決方案,簡化了容器應用程式的佈署和管理。結合 Terraform 的基礎架構即程式碼(IaC)能力,更能實作可重複、一致與安全的 Docker Swarm 叢集佈署。

本文將探討如何利用 Terraform 在 AWS 上自動化佈署 Docker Swarm 叢集,並分享玄貓在實務經驗中的一些獨到見解和技巧。

架構總覽

首先,讓我們用 圖表來視覺化我們將要佈署的架構:

  graph LR
    Bastion[Bastion]
    Manager[Manager]
    Swarm[Swarm]
    subgraph VPC[VPC 10.1.0.0/16]
        subgraph Public Subnet
            Bastion --> Swarm
            Bastion --> Manager
        end
        subgraph Private Subnet
            Swarm --> S3[Swarm Tokens]
            Swarm --> SwarmWorker1[Swarm Worker 1]
            Swarm --> SwarmWorker2[Swarm Worker 2]
        end
    end

圖表説明: 此架構圖展示了 VPC 內的公開子網路和私有子網路。Bastion 主機位於公有子網路,用於安全地存取私有子網路中的 Swarm Manager。Swarm Manager 和 Worker 則位於私有子網路,並透過 S3 儲存 Swarm 權杖。

建立 Packer 範本

我們將使用 Packer 建立一個包含 Docker Engine 的 AMI。以下是一個 Packer 範本範例:

{
  "variables": {},
  "builders": [
    {
      "type": "amazon-ebs",
      "profile": "{{user `aws_profile`}}",
      "region": "{{user `region`}}",
      "instance_type": "{{user `instance_type`}}",
      "source_ami": "{{user `source_ami`}}",
      "ssh_username": "ec2-user",
      "ami_name": "docker-swarm-ami-{{timestamp}}",
      "ami_description": "Docker Engine AMI for Swarm"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "script": "./setup.sh"
    }
  ]
}

這個 Packer 範本定義瞭如何根據 Amazon Linux 2 建立一個 Docker Engine AMI。setup.sh 指令碼用於安裝 Docker Community Edition,並將 ec2-user 加入 Docker 群組,使其無需 sudo 即可執行 Docker 命令。ami_name 使用時間戳記確保每次構建的 AMI 都是唯一的。

setup.sh 中,我們可以加入更多客製化設定,例如安裝特定版本的 Docker Compose 或設定 Docker daemon 的引數。

使用 Terraform 佈署基礎架構

接下來,我們使用 Terraform 佈署 VPC、子網路、安全群組以及 Swarm Manager 和 Worker 的 Auto Scaling Group (ASG)。

定義 VPC 和子網路

resource "aws_vpc" "swarm_vpc" {
  cidr_block = "10.1.0.0/16"
  tags = {
    Name = "swarm-vpc"
  }
}

resource "aws_subnet" "public_subnet" {
  vpc_id            = aws_vpc.swarm_vpc.id
  cidr_block        = "10.1.1.0/24"
  availability_zone = var.availability_zones[0]
  tags = {
    Name = "swarm-public-subnet"
  }
}

resource "aws_subnet" "private_subnet" {
  vpc_id            = aws_vpc.swarm_vpc.id
  cidr_block        = "10.1.2.0/24"
  availability_zone = var.availability_zones[1]
  tags = {
    Name = "swarm-private-subnet"
  }
}

這段程式碼定義了 VPC 和兩個子網路,一個公開子網路用於 Bastion 主機,一個私有子網路用於 Swarm Manager 和 Worker。將資源佈署在不同的可用區域可以提高容錯能力。

建立 Swarm Manager 啟動組態

resource "aws_launch_configuration" "swarm_manager_launch_config" {
  # ... (其他設定)

  user_data = base64encode(templatefile("./user_data_manager.sh", {
    s3_bucket = aws_s3_bucket.swarm_tokens.bucket
  }))
}

這裡定義了 Swarm Manager 的啟動組態,user_data 部分使用 templatefile 函式讀取 user_data_manager.sh 指令碼,並將 S3 bucket 名稱作為變數傳入。這個指令碼將在 Swarm Manager 啟動時執行,負責初始化 Swarm 叢集並將權杖儲存到 S3。

在後續的文章中,玄貓將會探討如何設定安全群組、使用者資料以及 Jenkins CI Pipeline 的整合,敬請期待!

slackSend (color: '#BADA55', message: "JOB '${env.JOB_NAME}' (${env.BUILD_NUMBER}): ${currentBuild.currentResult}")
  • 此程式碼片段使用 slackSend 方法傳送 Slack 通知。
  • color 引數指定訊息的顏色。
  • message 引數指定訊息的文字組。
  • ${env.JOB_NAME}${env.BUILD_NUMBER}${currentBuild.currentResult} 變數會插入任務名稱、建置編號和建置結果。

此程式碼片段會傳送 Slack 通知,其中包含任務名稱、建置編號和建置結果。顏色已設定為綠色 (#BADA55),但您可以根據建置狀態變更顏色。例如,如果建置失敗,您可以將顏色設定為紅色 (#FF0000)。

將此程式碼片段新增至您的 Jenkinsfile 中的 post 區段。post 區段中的程式碼會在建置完成後執行,無論建置成功或失敗。

pipeline {
    agent any
    stages {
        // ... your stages here
    }
    post {
        always {
            slackSend (color: '#BADA55', message: "JOB '${env.JOB_NAME}' (${env.BUILD_NUMBER}): ${currentBuild.currentResult}")
        }
    }
}

現在,每當您的 Pipeline 完成時,您都會收到 Slack 通知,告知您建置狀態。

處理建置失敗

當建置失敗時,除了傳送 Slack 通知外,您還可以採取其他動作。例如,您可以傳送電子郵件通知、建立問題單或將建置標記為不穩定。

以下是如何在建置失敗時傳送紅色 Slack 通知並標記建置為不穩定的範例:

pipeline {
    agent any
    stages {
        // ... your stages here
    }
    post {
        failure {
            slackSend (color: '#FF0000', message: "JOB '${env.JOB_NAME}' (${env.BUILD_NUMBER}): ${currentBuild.currentResult}")
            currentBuild.result = 'UNSTABLE'
        }
    }
}
  • 此程式碼片段使用 failure 區塊在建置失敗時執行特定動作。
  • 它會傳送紅色 Slack 通知,並使用 currentBuild.result = 'UNSTABLE' 將建置標記為不穩定。

透過將 Slack 通知整合到您的 Jenkins CI Pipeline 中,您可以隨時掌握建置狀態,並在發生問題時立即採取行動。

透過遵循這些步驟,您可以將 Jenkins CI Pipeline 與 Slack 通知整合,以實作 Docker Swarm 應用程式的持續佈署。這可讓您隨時掌握建置狀態,並在發生問題時立即採取行動。

本文説明如何使用 Jenkins CI Pipeline 將 Slack 通知整合到 Docker Swarm 應用程式的持續佈署中。透過遵循這些步驟,您可以建立一個強大的 CI/CD Pipeline,讓您隨時掌握建置狀態,並在發生問題時立即採取行動。在實務經驗中,我發現將 Slack 通知整合到 CI/CD Pipeline 中對於保持團隊資訊透明化和快速解決問題非常有幫助。

  vpc_config {
    subnet_ids = data.aws_subnets.private.ids
  }

  enabled_cluster_log_types = [
    "api",
    "audit",
    "authenticator",
    "controllerManager",
    "scheduler",
  ]

  version = "1.21"

  tags = {
    Environment = "sandbox"
  }
}

這段 Terraform 程式碼定義了一個 AWS EKS 叢集資源。name 引數設定叢集名稱,role_arn 指定叢集使用的 IAM 角色,vpc_config 設定叢集的 VPC 設定,套件括子網路 ID。enabled_cluster_log_types 啟用各種叢集日誌型別,version 設定 Kubernetes 版本,tags 新增標籤以便於管理。

接下來,我們需要定義 EKS 叢集所需的 IAM 角色和策略。在名為 iam.tf 的檔案中,新增以下程式碼:

resource "aws_iam_role" "cluster_role" {
  name = "eks-cluster-role-${var.cluster_name}"

  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "eks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSClusterPolicy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  role       = aws_iam_role.cluster_role.name
}

resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSVPCResourceController" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
  role       = aws_iam_role.cluster_role.name
}

這段程式碼建立了 EKS 叢集所需的 IAM 角色和策略。aws_iam_role 資源定義了 IAM 角色,assume_role_policy 指定哪些主體可以承擔此角色。這裡,我們允許 EKS 服務承擔此角色。aws_iam_role_policy_attachment 資源將必要的策略附加到角色,套件括 AmazonEKSClusterPolicyAmazonEKSVPCResourceController,這些策略授予 EKS 叢集管理 VPC 資源的許可權。

透過以上設定,我們已經完成了 EKS 叢集的 Terraform 設定。執行 terraform apply 後,EKS 叢集就會在 AWS 上建立。後續文章將會介紹如何將應用程式佈署到這個 EKS 叢集。

這個 EKS 叢集設定提供了高度可設定性和安全性,並利用 Terraform 的基礎架構即程式碼功能進行管理。相較於其他工具,Terraform 提供更精細的控制和可重複性,更適合複雜的 Kubernetes 叢集佈署。

在實際應用程式中,我建議根據需求調整 EKS 叢集的版本和設定,例如啟用其他日誌型別或設定自動擴充功能功能。此外,也建議定期更新 Kubernetes 版本以取得最新的安全性和功能更新。

def region = 'AWS REGION'
def accounts = [master:'production', preprod:'staging', develop:'sandbox']
node('master'){
  stage('Checkout'){
    checkout scm
  }
  stage('Authentication'){
    sh "aws eks update-kubeconfig --name ${accounts[env.BRANCH_NAME]} --region ${region}"
  }
  stage('Deploy'){
    sh 'kubectl apply -f deployments/'
  }
}

這個 Jenkins Pipeline 指令碼定義了三個階段:

  1. Checkout: 從版本控制系統中簽出程式碼。
  2. Authentication: 使用 aws eks update-kubeconfig 命令設定 kubectl,以便與正確的 EKS 叢集進行互動。accounts 變數用於根據當前分支名稱選擇對應的 EKS 叢集名稱。
  3. Deploy: 使用 kubectl apply -f deployments/ 命令將 deployments 資料夾中的所有 Kubernetes 資源佈署到叢集中。

在推播 Jenkinsfile 和佈署檔案到 Git 儲存函式庫之前,需要在 Jenkins Master 上安裝 kubectlaws-iam-authenticator,並設定 AWS 憑證,確保 Jenkins 可以存取 EKS 叢集。

設定 Kubernetes 服務

為了讓應用程式的不同元件能夠相互通訊,並將應用程式暴露給外部流量,需要設定 Kubernetes 服務。服務提供了一個穩定的入口點,即使 Pod 被重新安排或重新啟動,也可以存取應用程式。

以下是一個簡單的 Kubernetes 服務範例,用於暴露 movies-marketplace 應用程式:

apiVersion: v1
kind: Service
metadata:
  name: movies-marketplace
  namespace: watchlist
spec:
  selector:
    app: movies-marketplace
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer
  • apiVersion: Kubernetes API 版本。
  • kind: 資源型別,這裡是 Service。
  • metadata.name: 服務的名稱。
  • metadata.namespace: 服務所在的名稱空間空間。
  • spec.selector: 選擇器,用於比對 Pod。這裡選擇所有帶有 app: movies-marketplace 標籤的 Pod。
  • spec.ports: 服務暴露的連線埠列表。這裡暴露 80 連線埠,並將流量轉發到 Pod 的 3000 連線埠。
  • spec.type: 服務型別,這裡是 LoadBalancer,表格示 Kubernetes 將自動設定一個負載平衡器來暴露服務。

這個服務定義將建立一個負載平衡器,並將流量轉發到所有比對 app: movies-marketplace 標籤的 Pod 的 3000 連線埠。

設定 Ingress 控制器

除了使用服務的 LoadBalancer 類別型,還可以使用 Ingress 控制器來管理外部存取。Ingress 控制器提供更高階的路由和流量管理功能,例如根據路徑的路由、TLS/SSL 終止和流量分割。

設定 Ingress 控制器需要佈署一個 Ingress 控制器到 Kubernetes 叢集中,並建立 Ingress 資源來定義路由規則。

在實際應用程式中,我建議使用 Helm 圖表s 來管理 Kubernetes 應用程式的佈署,並使用 GitOps 的方法來管理基礎設施即程式碼。這些最佳實踐可以提高佈署效率和可靠性。

stage('Deploy'){
    sh "helm upgrade watchlist ./watchlist -f values.override.yaml \
        --set metadata.jenkins.buildTag=${env.BUILD_TAG} \
        --set metadata.git.commitId=$(git rev-parse --short HEAD)"
}
  • 此程式碼片段展示瞭如何使用 Helm 命令來升級 Kubernetes 應用程式。
  • helm upgrade 命令會升級現有的 Helm release。
  • --set 旗標用於設定 Helm 圖表中的值。
  • ${env.BUILD_TAG}$(git rev-parse --short HEAD) 分別用於設定 Jenkins 建置 ID 和 Git 認可 ID。

現在,將程式碼推播至 develop 分支,Jenkins 將會建置新的 Docker 映像,並使用更新的映像升級 Helm 圖表格。

透過結合 Helm 和 Jenkins,我們可以簡化 Kubernetes 應用程式的佈署流程。Helm 提供了可重複使用與易於管理的應用程式封裝方式,而 Jenkins 則自動化了建置和佈署流程。

這個流程描述瞭如何使用 Jenkins 和 Helm 佈署應用程式到 Kubernetes 叢集。透過 Helm 的套件管理能力和 Jenkins 的自動化流程,可以有效地管理和佈署應用程式。透過這個方法,開發者可以更專注於開發應用程式,而不用擔心佈署的複雜性。

在實際應用程式中,可以根據需求調整 values.yaml 檔案中的引數,例如 replica 數量、資源限制等等。此外,也可以使用 Helm 的其他功能,例如版本控制和回復,來更好地管理應用程式佈署。

透過以上步驟,我們成功地使用 Jenkins CI Pipeline 佈署了應用程式到 Kubernetes,並整合了 Helm。Helm 簡化了 Kubernetes 應用程式的佈署和管理,讓我們能更輕鬆地管理應用程式的生命週期。

在這個流程中,我們學習瞭如何使用 Helm 建立和管理圖表格,以及如何使用 Jenkins CI Pipeline 自動化佈署流程。這是一個非常實用的技能,可以幫助開發者更有效率地佈署和管理 Kubernetes 應用程式。

stage('Package') {
    parallel {
        stage('Build Docker Image') {
            agent {
                label 'docker'
            }
            steps {
                sh "docker build -t ${env.REGISTRY}/${env.ORG}/${env.APP}:${env.TAG} ."
                withCredentials([usernamePassword(credentialsId: "${env.REGISTRY_CREDENTIAL}", passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
                    sh "docker login ${env.REGISTRY} -u ${REGISTRY_USERNAME} -p ${REGISTRY_PASSWORD}"
                    sh "docker push ${env.REGISTRY}/${env.ORG}/${env.APP}:${env.TAG}"
                }
            }
        }
        stage('Package Helm 圖表') {
            steps {
                sh "helm package ./chart -d ./charts"
                withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_TOKEN')]) {
                    sh """
                      curl -u ${GITHUB_USERNAME}:${GITHUB_TOKEN} -X POST -H 'Accept: application/vnd.github.v3+json' \\
                      https://api.github.com/repos/${GITHUB_USERNAME}/watchlist-charts/releases \\
                      -d '{"tag_name": "v${env.TAG}", "name": "v${env.TAG}", "body": "Release created by Jenkins Pipeline v${env.TAG}"}'
                    """
                    sh """
                      repo_url=\$(curl -u ${GITHUB_USERNAME}:${GITHUB_TOKEN} -X GET -H 'Accept: application/vnd.github.v3+json' \\
                      https://api.github.com/repos/${GITHUB_USERNAME}/watchlist-charts/releases/tags/v${env.TAG} | jq -r '.upload_url')
                    """
                    sh "upload_url=\${repo_url%\\{?name,label\\}}"
                    sh "curl -u ${GITHUB_USERNAME}:${GITHUB_TOKEN} -X POST -H 'Accept: application/vnd.github.v3+json' \${upload_url}?name=watchlist-${env.TAG}.tgz \\
                    --data-binary @./charts/watchlist-${env.TAG}.tgz"
                    sh "helm repo index ./charts --url https://${GITHUB_USERNAME}.github.io/watchlist-charts/"
                    sh "git add ."
                    sh "git commit -m 'update index.yaml'"
                    sh "git push origin master"
                }
            }
        }
    }
}

這段程式碼定義了 Jenkins Pipeline 中的 “Package” 階段,它使用 parallel 指令平行執行兩個子階段:構建 Docker 映像檔和封裝 Helm 圖表。

構建 Docker 映像檔階段:

  • 使用 agent { label 'docker' } 指定在具有 Docker 的節點上執行此階段。
  • 使用 docker build 命令構建 Docker 映像檔,並使用環境變數設定映像檔名稱和標籤。
  • 使用 withCredentials 讀取 Docker Registry 的憑證,然後使用 docker login 登入 Registry。
  • 使用 docker push 將映像檔推播到 Registry。

封裝 Helm 圖表 階段:

  • 使用 helm package 命令封裝 Helm 圖表。
  • 使用 withCredentials 讀取 GitHub 的憑證。
  • 使用 curl 命令與 GitHub API 互動,建立一個新的 Release,並上載封裝好的 Helm 圖表。
  • 使用 helm repo index 命令更新 Helm 圖表 儲存函式庫的索引檔 index.yaml
  • 將更新的 index.yaml 遞交到 Git 儲存函式庫並推播到遠端。

這兩個階段平行執行可以縮短構建時間,提高效率。

接下來,更新 values.yaml 檔案,加入 metadata 區塊,用於儲存 Jenkins 建置標籤和 Git 遞交 ID:

metadata:
  jenkins:
    buildTag: ""
  git:
    commitId: ""

這段程式碼在 values.yaml 檔案中新增了一個 metadata 區塊,其中包含兩個欄位:jenkins.buildTaggit.commitId。這些欄位將用於儲存 Jenkins 建置標籤和 Git 遞交 ID,以便在佈署時將這些資訊新增到 Kubernetes 資源的註解中。

現在,我們可以更新 Jenkinsfile,使用 Helm 命令而不是 kubectl。由於我們的應用程式圖表已經安裝,我們將使用 helm upgrade 命令來升級圖表格。此命令將接受要覆寫的值作為引數,並設定來自 Jenkins 環境變數 BUILD_TAGcommitID() 方法的註解值,如下所示:

stage('Deploy'){
  sh """
    helm upgrade --install watchlist \\
      ./watchlist -f values.override.yaml \\
      --set metadata.jenkins.buildTag=${env.BUILD_TAG} \\
      --set metadata.git.commitId=${commitID()}
  """
}

這段程式碼定義了 Jenkins Pipeline 中的 “Deploy” 階段,使用 helm upgrade --install 命令升級或安裝 Helm 圖表。

  • helm upgrade --install watchlist ./watchlist:升級名為 watchlist 的 Helm 圖表,如果 圖表 不存在則安裝它。./watchlist 指定 圖表 的目錄。
  • -f values.override.yaml:使用 values.override.yaml 檔案中的值覆寫 圖表 的預設值。
  • --set metadata.jenkins.buildTag=${env.BUILD_TAG}:設定 metadata.jenkins.buildTag 的值為 Jenkins 的建置標籤。
  • --set metadata.git.commitId=${commitID()}:設定 metadata.git.commitId 的值為 Git 的遞交 ID。

使用 helm upgrade --install 命令可以確保 圖表 始終安裝在叢集中,即使它是第一次佈署。

Helm 會嘗試執行最少侵入性的升級。它只會更新自上次發布以來已更改的專案。

將更改推播到 develop 分支。現在,develop 分支上的每個更改都會構建一個新的 Helm 圖表格,並在沙盒叢集上建立一個新的發布。如果 Docker 映像檔已更改,Kubernetes 滾動更新將提供零停機時間佈署更改的功能。

如果發布期間發生任何意外情況,可以使用 helm rollback 命令輕鬆回復到先前的發布。

透過以上步驟,我們成功地將 Helm 整合到 Jenkins CI Pipeline 中,實作了 Kubernetes 應用程式的自動化佈署和管理。在實務經驗中,我建議使用 Helm 來管理 Kubernetes 應用程式,並使用 Jenkins X 來簡化 CI/CD 工作流程。

這個流程圖展示了使用 Helm 和 Jenkins 佈署應用程式的流程:

  graph LR
    B[B]
    A[程式碼變更] --> B{Jenkins 觸發構建};
    B --> C[構建 Docker 映像檔];
    C --> D[封裝 Helm 圖表];
    D --> E[推播 Helm 圖表 到儲存函式庫];
    E --> F[Helm 佈署/升級應用程式];
    F --> G[Kubernetes 應用程式執行];

希望這篇文章能幫助你理解如何在 Jenkins CI Pipeline 中使用 Helm 佈署 Kubernetes 應用程式。

module "MoviesStoreSearchMovies" {
  source  = "./modules/function"
  name    = "MoviesStoreSearchMovies"
  handler = "src/movies/search/index.handler"
  runtime = "nodejs14.x"

  environment = {
    TABLE_NAME = aws_dynamodb_table.movies.id
  }
}

這段 Terraform 程式碼定義了四個 Lambda 函式模組:MoviesLoaderMoviesParserMoviesStoreListMoviesMoviesStoreSearchMovies。每個模組都使用了 ./modules/function 作為來源,這表示它們分享相同的基礎組態。name 屬性指定了函式的名稱,handler 屬性指定了函式的入口點,runtime 屬性指定了函式的執行環境。environment 屬性設定了函式的環境變數,例如 SQS 佇列 URL 和 DynamoDB 表格名稱。這些變數允許函式與其他 AWS 服務互動。

每個 Lambda 函式模組都有一些重要的引數:

  • source:指定模組的來源。在這個例子中,所有函式都使用同一個模組 ./modules/function,這有助於保持程式碼的一致性和可維護性。
  • name:Lambda 函式的名稱。
  • handler:函式的入口點。這個值取決於函式的執行環境和程式碼結構。
  • runtime:函式的執行環境,例如 python3.7go1.xnodejs14.x
  • environment:設定函式的環境變數。這些變數可以讓函式存取必要的資源,例如資料函式庫連線字串或其他 AWS 服務的 URL。

透過使用模組,我們可以避免重複程式碼,並更容易地管理多個 Lambda 函式。此外,我們可以根據需要自定義每個函式的引數,例如執行環境和環境變數。

在實際應用程式中,我們可以根據需要新增更多 Lambda 函式模組,並根據每個函式的需求設定不同的引數。這使得 Terraform 成為管理無伺服器應用程式基礎設施的強大工具。

接下來,我們需要設定 Jenkins CI Pipeline 來自動化 Lambda 函式的佈署。Pipeline 應該包含以下步驟:

  1. 程式碼簽出:從版本控制系統中簽出最新的程式碼。
  2. Terraform 初始化:初始化 Terraform 工作目錄。
  3. Terraform 規劃:預覽將要進行的變更。
  4. Terraform 應用程式:應用變更並佈署 Lambda 函式。

透過使用 Jenkins CI Pipeline 和 Terraform,我們可以自動化 Lambda 函式的佈署,並確保基礎設施的可靠性和一致性。

以下是一個 Jenkinsfile 的範例,展示瞭如何使用 Terraform 佈署 Lambda 函式:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        stage('Terraform Init') {
            steps {
                sh 'terraform init'
            }
        }
        stage('Terraform Plan') {
            steps {
                sh 'terraform plan -out=tfplan'
            }
        }
        stage('Terraform Apply') {
            steps {
                sh 'terraform apply tfplan'
            }
        }
    }
}

這段 Jenkinsfile 程式碼定義了一個 CI/CD Pipeline,用於自動化 Lambda 函式的佈署。Pipeline 套件含四個階段:

  • Checkout:從版本控制系統中簽出最新的程式碼。
  • Terraform Init:初始化 Terraform 工作目錄。這一步會下載必要的外掛程式和模組。
  • Terraform Plan:預覽將要進行的變更。這一步會生成一個執行計劃,顯示 Terraform 將要建立、修改或刪除的資源。
  • Terraform Apply:應用程式變更並佈署 Lambda 函式。這一步會執行 terraform plan 生成的執行計劃。

這個 Pipeline 使用了 sh 步驟來執行 Terraform 命令。terraform init 命令初始化 Terraform 工作目錄,terraform plan 命令生成執行計劃,terraform apply 命令應用程式變更。

透過使用 Jenkins Pipeline 和 Terraform,我們可以自動化 Lambda 函式的佈署,並確保基礎設施的可靠性和一致性。此外,terraform plan 命令可以幫助我們預覽變更,並避免意外的錯誤。

這個 Pipeline 可以根據實際需求進行修改,例如新增測試階段或使用不同的 Terraform 命令。

這個範例展示瞭如何使用 Jenkins 和 Terraform 協同工作來自動化 Lambda 函式的佈署。透過使用這些工具,我們可以簡化佈署流程,並提高效率。

在實際應用程式中,我們還可以加入更多步驟,例如程式碼測試、安全性掃描和效能測試,以確保 Lambda 函式的品質和安全性。

總之,結合 Jenkins 和 Terraform 可以建立一個強大的 CI/CD Pipeline,用於自動化 Lambda 函式的佈署和管理。

def functions = [
    'list': 'movies/findAll',
    'search': 'movies/findOne',
    'favorites-view': 'favorites/findAll',
    'favorites-add': 'favorites/insert'
]
def bucket = 'deployment-packages-watchlist'
def region = 'YOUR_AWS_REGION'

node('workers') {
    try {
        stage('Checkout') {
            checkout scm
        }
        stage('Build') {
            functions.each { functionName, functionPath ->
                sh "mkdir -p ${functionName}"
                sh "cp -r ${functionPath}/* ${functionName}/"
                sh "cp package*.json ${functionName}/"

                docker.image('node:14').inside {
                    dir("${functionName}") {
                        sh "npm install --prod=only"
                    }
                    sh "cp -r ${functionName}/node_modules ."
                }

                sh "zip -r ${functionName}.zip ${functionName}"
                sh "rm -rf ${functionName}"
            }

        }
        stage('Push') {
            functions.each { functionName, functionPath ->
              sh "aws s3 cp ${functionName}.zip s3://${bucket}/movies-store/${functionName}/"
              sh "rm -rf ${functionName}.zip"
            }
        }
    } catch (e) {
        currentBuild.result = 'FAILED'
        throw e
    } finally {
        notifySlack(currentBuild.result)
        cleanWs()
    }
}

此 Jenkins Pipeline 指令碼定義了建置和推播多個 AWS Lambda 函式佈署套件的流程,這些函式位於一個單體儲存函式庫 (mono-repo) 中。

  • functions 變數定義了一個 map,其中鍵是函式的簡稱,值是函式程式碼的相對路徑。
  • bucketregion 變數分別指定了 S3 儲存桶和 AWS 區域。
  • Checkout 階段簽出程式碼。
  • Build 階段迭代 functions map 中的每個函式:
    • 建立一個以函式名命名的臨時目錄。
    • 將函式程式碼和 package.json 檔案複製到臨時目錄。
    • 使用 node:14 Docker 映像檔,在臨時目錄中執行 npm install --prod=only 安裝生產環境依賴項。 --prod=only 標誌確保只安裝生產環境依賴項,從而減小佈署套件的大小。
    • node_modules 目錄從容器複製到工作區。
    • 建立一個包含函式程式碼和依賴項的 zip 檔案。
    • 刪除臨時目錄。
  • Push 階段迭代 functions map 中的每個函式:
    • 將 zip 檔案上載到 S3 儲存桶,路徑為 s3://{bucket}/movies-store/{functionName}/
    • 刪除 zip 檔案。
  • try-catch-finally 塊確保即使發生錯誤,Pipeline 也能正確清理工作區並傳送 Slack 通知。
  • cleanWs() 方法清理 Jenkins 工作區。

這個指令碼的優點是它將所有函式的建置和佈署邏輯集中在一處,便於管理和維護。它還利用 Docker 來確保建置環境的一致性,並使用 --prod=only 標誌來最佳化佈署套件的大小。

在 Jenkins 上建立一個新的多分支 Pipeline 任務,並將其指向 movies-store 儲存函式庫。Jenkins 將會掃描儲存函式庫中的 Jenkinsfile 並開始建置。建置完成後,您應該會在 S3 儲存桶中看到四個新的 zip 檔案,每個函式各一個。

透過使用單一儲存函式庫方法,我們可以簡化建置流程,並確保所有 Lambda 函式都使用相同的依賴項版本。這有助於減少衝突並提高應用程式的穩定性。

在下一篇文章中,我們將探討如何使用 Terraform 將這些佈署套件佈署到 AWS Lambda。

aws lambda create-alias --function-name MoviesStoreViewFavorites --name \
sandbox --function-version 1

建立後,新的別名應該會新增至「限定詞」下拉式清單下的「別名」清單。我們可以更新 Jenkinsfile 以直接更新別名。使用下列程式碼更新「佈署」階段。它會更新 Lambda 函式程式碼、釋出新版本,然後將對應於目前 Git 分支 (master 分支 = 生產別名、preprod 分支 = 預備別名、develop 分支 = 沙箱別名) 的別名指向新佈署的版本。

sh "aws lambda update-function-code --function-name ${it} \
  --s3-bucket ${bucket} --s3-key ${it}/${fileName}.zip \
  --region ${region}"
def version = sh(
  script: "aws lambda publish-version --function-name ${it} \
    --description ${fileName} \
    --region ${region} | jq -r '.Version'",
  returnStdout: true
).trim()
if (env.BRANCH_NAME in ['master','preprod','develop']){
  sh "aws lambda update-alias --function-name ${it} \
    --name ${environments[env.BRANCH_NAME]} \
    --function-version \${version} \
    --region ${region}"
}

這段 Groovy 程式碼片段展現瞭如何利用 Jenkins Pipeline 和 AWS CLI 管理 Lambda 函式的版本和別名。 首先,它使用 aws lambda update-function-code 命令將新的程式碼佈署到 Lambda 函式,程式碼來自 S3 儲存桶中指定的路徑。 接著,它使用 aws lambda publish-version 命令發布一個新的 Lambda 函式版本,並使用 jq 工具從 JSON 輸出中提取版本號。 最後,它根據當前的 Git 分支名稱 (master、preprod 或 develop) 使用 aws lambda update-alias 命令更新對應的別名,使其指向新發布的版本。 environments 變數應是一個 map,用於將分支名稱對應到別名名稱。

透過使用別名,我們可以避免在每次佈署新版本時都修改 API Gateway 的設定。

設定 API Gateway 階段變數

在 API Gateway 中,階段變數是一種在執行階段組態 API 列為的機制。我們可以利用階段變數來動態指定 Lambda 函式的別名,從而實作多環境佈署。

在 API Gateway 主控台上,導覽至您的 API,選擇所需的階段,然後在「階段變數」區段中設定一個變數,例如 LambdaAlias。將其值設定為您想要使用的 Lambda 別名,例如 sandboxpreprodprod

在整合請求中,使用 ${stageVariables.LambdaAlias} 來參照這個變數。這樣,API Gateway 就會根據階段變數的值來呼叫不同的 Lambda 函式版本。

透過在 API Gateway 的整合請求中使用 ${stageVariables.LambdaAlias},我們將 Lambda 函式的版本選擇委託給了階段變數。 這使得我們可以在不修改 API Gateway 設定的情況下,透過更改階段變數的值來切換不同的 Lambda 函式版本,從而簡化了多環境佈署的流程。

設定 S3 網站託管

S3 除了可以用於儲存 Lambda 函式的佈署套件外,還可以託管靜態網站。 您可以將網站的 HTML、CSS、JavaScript 和其他靜態檔案上載到 S3 儲存桶,並將儲存桶設定為網站託管。

在 S3 主控台上,選擇您的儲存桶,然後在「屬性」頁籤中啟用「靜態網站託管」。 設定索引檔案和錯誤檔案,例如 index.htmlerror.html

在 S3 上託管靜態網站是一種簡單與經濟高效的方式。 透過設定儲存桶的靜態網站託管功能,您可以直接透過 S3 的 URL 存取您的網站。 設定索引檔案和錯誤檔案可以確保使用者存取網站時看到正確的內容。

使用 Jenkins Input Step 外掛程式要求使用者驗證

在某些情況下,我們可能需要在佈署過程中加入人工驗證步驟。 例如,在佈署到生產環境之前,可能需要管理員的批准。 Jenkins 的 Input Step 外掛程式可以實作這個功能。

在 Jenkinsfile 中,使用 input 步驟來暫停 Pipeline 的執行,並等待使用者的輸入。 您可以設定一個提示訊息,例如 “是否確認佈署到生產環境?",並指定需要批准的使用者。

stage('佈署到生產環境') {
  input message: '是否確認佈署到生產環境?', submitter: 'admin'
  // 佈署到生產環境的程式碼
}

input 步驟提供了一種在 Jenkins Pipeline 中引入人工干預的機制。 這在需要人工驗證或批准的場景下非常有用,例如佈署到生產環境。 透過指定 submitter 引數,可以限制只有特定使用者才能批准佈署。

透過結合 Lambda 版本、別名、API Gateway 階段變數、S3 網站託管和 Jenkins Input Step 外掛程式,我們可以構建一個高度自動化與安全的 CI/CD 流程。

這個流程可以根據您的具體需求進行調整和擴充套件。 例如,您可以使用 Terraform 或 CloudFormation 等基礎設施即程式碼工具來管理您的 AWS 資源,並使用更精細的版本控制策略。 持續學習和改進您的 CI/CD 流程是確保軟體交付效率和品質的關鍵。

CREATE DATABASE "instances"
CREATE DATABASE "containers"

接下來,我們需要安裝並設定 Telegraf 代理伺服器程式,以從 Jenkins 執行個體收集計量。Telegraf 是一個外掛程式驅動程式的代理程式,用於收集各種計量。它支援各種輸入外掛程式,例如系統、Docker、Redis 等。它還支援各種輸出外掛程式,例如 InfluxDB、Graphite、Prometheus 等。

在 Jenkins Master 和所有 Jenkins Worker 上安裝 Telegraf。您可以參考 Telegraf 官方檔案來取得安裝説明。安裝完成後,您需要設定 Telegraf 以將計量傳送到 InfluxDB。編輯位於 /etc/telegraf/telegraf.conf 的 Telegraf 設定檔。

[[outputs.influxdb]] 區段下,設定 InfluxDB 伺服器的 URL、資料函式庫名稱和使用者名稱/密碼。

[[outputs.influxdb]]
  urls = ["http://<influxdb-server-ip>:8086"]
  database = "instances"
  username = "<influxdb-username>"
  password = "<influxdb-password>"

[[inputs.cpu]][[inputs.mem]][[inputs.disk]][[inputs.net]][[inputs.system]] 區段下,取消註解以啟用這些輸入外掛程式。這些外掛程式將收集 CPU 使用率、記憶體使用量、磁碟使用量、網路流量和系統負載等計量。

[[inputs.cpu]]
  percpu = true
  totalcpu = true
  collect_cpu_time = false
  report_active = false

[[inputs.mem]]

[[inputs.disk]]
  ignore_fs = ["tmpfs", "devtmpfs", "devfs", "overlay", "aufs", "squashfs"]

[[inputs.net]]

[[inputs.system]]

儲存設定檔並重新啟動 Telegraf 服務,以套用變更。

sudo systemctl restart telegraf

現在,Telegraf 將開始收集計量並將它們傳送到 InfluxDB。

最後一步是設定 Grafana 以視覺化儲存在 InfluxDB 中的計量。Grafana 是一個開放原始碼的視覺化平台,可讓您建立動態和互動式的儀錶板。

在與 InfluxDB 相同的執行個體或單獨的執行個體上安裝 Grafana。您可以參考 Grafana 官方檔案來取得安裝説明。安裝完成後,開啟 Grafana Web 介面。

新增資料來源並設定 InfluxDB 作為資料來源。設定 URL、資料函式庫名稱和使用者名稱/密碼。

建立新的儀錶板並新增面板。您可以使用各種面板型別,例如圖表格、單一統計值、表格等,來視覺化計量。

例如,您可以建立一個圖表面板來顯示 CPU 使用率,並使用單一統計值面板來顯示可用記憶體。您可以自訂儀錶板並新增更多面板,以監控 Jenkins 叢集的各種導向。

這段程式碼示範瞭如何使用 Telegraf、InfluxDB 和 Grafana 建立一個監控堆積疊,用於監控 Jenkins 叢集的健康情況。Telegraf 作為一個代理伺服器程式,從 Jenkins Master 和 Worker 伺服器收集各種系統計量,例如 CPU 使用率、記憶體使用量、磁碟使用量和網路流量。收集到的計量會傳送到 InfluxDB,一個時間序列資料函式庫,用於儲存和查詢計量資料。最後,Grafana 用於視覺化儲存在 InfluxDB 中的計量資料,並建立動態和互動式的儀錶板,以監控 Jenkins 叢集的效能和健康情況。透過這個監控堆積疊,您可以輕鬆地追蹤 Jenkins 叢集的資源使用情況,並在出現問題時及時採取行動。

這個設定完成後,您將擁有一個功能強大的監控堆積疊,可以追蹤 Jenkins 叢集的健康情況和效能。您可以使用這個資訊來識別潛在問題並在它們造成任何重大問題之前解決它們。

在實務經驗中,我建議使用容器化解決方案來佈署和管理監控堆積疊,例如 Docker 和 Kubernetes。這可以簡化佈署流程,並確保監控堆積疊的高用性和可擴充功能性。此外,您還可以設定警示,以便在計量超過特定閾值時收到通知。

# 下載 Telegraf 安裝指令碼
wget https://dl.influxdata.com/telegraf/releases/telegraf_1.19.0-1_amd64.deb

# 安裝 Telegraf
sudo dpkg -i telegraf_1.19.0-1_amd64.deb

# 啟用並啟動 Telegraf 服務
sudo systemctl enable telegraf
sudo systemctl start telegraf

這段 bash 指令碼首先使用 wget 下載 Telegraf 1.19.0 版本的安裝套件。接著,使用 dpkg 命令安裝下載的 .deb 安裝套件。最後,使用 systemctl 命令啟用並啟動 Telegraf 服務,確保 Telegraf 在系統啟動時自動執行,並開始收集系統指標。

設定 Telegraf 以收集 Jenkins 指標並將其傳送到 InfluxDB。建立 /etc/telegraf/telegraf.conf 檔案,並新增以下設定:

[agent]
  interval = "10s"
  round_interval = true
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = "0s"
  flush_interval = "10s"
  flush_jitter = "0s"
  precision = ""
  hostname = ""
  omit_hostname = false

[[outputs.influxdb_v2]]
  urls = ["http://<INFLUXDB_IP>:8086"] # 將 <INFLUXDB_IP> 替換為 InfluxDB 伺服器的 IP 地址
  token = "<INFLUXDB_TOKEN>" # 將 <INFLUXDB_TOKEN> 替換為您的 InfluxDB 權杖
  organization = "my-org"
  bucket = "instances"

[[inputs.cpu]]
  percpu = true
  totalcpu = true
  collect_cpu_time = false
  report_active = false

[[inputs.mem]]

[[inputs.disk]]
  ignore_fs = ["tmpfs", "devtmpfs", "devfs", "overlay", "aufs", "squashfs"]

[[inputs.system]]

[[inputs.net]]

這段 Telegraf 設定檔定義了資料收集的間隔、批次大小等。outputs.influxdb_v2 區段設定了 InfluxDB 的連線資訊,套件括 URL、權杖、組織和儲存桶。inputs 區段定義了要收集的指標,套件括 CPU、記憶體、磁碟、系統和網路。

重新啟動 Telegraf 服務以套用變更:

sudo systemctl restart telegraf

這個命令重新啟動 Telegraf 服務,使新的設定檔生效,開始收集並傳送指標到 InfluxDB。

現在,Telegraf 將收集 Jenkins 執行個體指標,並將其傳送到 InfluxDB。您可以使用 Grafana 建立儀錶板來視覺化這些指標。