在 CI/CD 流程中,Jenkins 扮演著至關重要的角色。為了避免單點故障並提升構建效率,本文將探討如何利用 Terraform 在 AWS 上佈署可自動擴充套件的 Jenkins 工作節點池。此方案結合了 AWS Auto Scaling 和 CloudWatch 的功能,能根據 CPU 使用率動態調整工作節點數量,在滿足構建需求的同時,有效控制成本。文章詳細說明瞭如何設定啟動組態、安全群組、使用者資料指令碼以及自動擴充套件策略,並提供程式碼範例和圖表說明,引導讀者逐步實作。透過 Packer 建立的 Jenkins 工作節點 AMI 確保環境一致性,並使用 SSH 讓工作節點加入 Jenkins 叢集。最後,透過 Stress 工具驗證自動擴充套件策略的有效性,確保系統能彈性應對不同負載。

使用 Terraform 自動擴充套件 Jenkins 工作節點池

在實際生產環境中,單一 Jenkins 例項會成為單點故障。當該例項當機或因過多構建任務而過載時,開發人員將無法交付發行版本。解決方案是執行一個 Jenkins 工作節點叢集,並根據資源利用率動態調整叢集大小。

啟動組態

雖然可以將 Jenkins 工作節點佈署為獨立的 EC2 例項,但為了實作自動還原,我們將依賴 AWS 的 Auto Scaling 群組功能。

建立啟動組態

首先,我們需要在 jenkins_workers.tf 檔案中宣告一個 aws_launch_configuration 資源,以描述如何組態每個 Jenkins 工作節點例項。

resource "aws_launch_configuration" "jenkins_workers_launch_conf" {
  name            = "jenkins_workers_config"
  image_id        = data.aws_ami.jenkins-worker.id
  instance_type   = var.jenkins_worker_instance_type
  key_name        = aws_key_pair.management.id
  security_groups = [aws_security_group.jenkins_workers_sg.id]
  user_data       = data.template_file.user_data_jenkins_worker.rendered

  root_block_device {
    volume_type           = "gp2"
    volume_size           = 30
    delete_on_termination = false
  }

  lifecycle {
    create_before_destroy = true
  }
}

自訂 Jenkins 工作節點 AMI

我們使用在第 4 章中用 Packer 建立的 Jenkins 工作節點 AMI。

data "aws_ami" "jenkins-worker" {
  most_recent = true
  owners      = ["self"]

  filter {
    name   = "name"
    values = ["jenkins-worker*"]
  }
}

設定安全群組

為了允許 Jenkins 主節點與工作節點之間的雙向連線,我們需要在安全群組中允許來自 Jenkins 主節點安全群組 ID 的 SSH 連線。

resource "aws_security_group" "jenkins_workers_sg" {
  name        = "jenkins_workers_sg"
  description = "Allow traffic on port 22 from Jenkins master SG"
  vpc_id      = aws_vpc.management.id

  ingress {
    from_port       = "22"
    to_port         = "22"
    protocol        = "tcp"
    security_groups = [aws_security_group.jenkins_master_sg.id, aws_security_group.bastion_host.id]
  }

  egress {
    from_port   = "0"
    to_port     = "0"
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name   = "jenkins_workers_sg"
    Author = var.author
  }
}

使用者資料指令碼

在每個 Jenkins 工作節點例項啟動時,將執行一個指令碼,該指令碼會將例項加入到 Jenkins 叢集中。

data "template_file" "user_data_jenkins_worker" {
  template = "${file("scripts/join-cluster.tpl")}"

  vars = {
    jenkins_url            = "http://${aws_instance.jenkins_master.private_ip}:8080"
    jenkins_username       = var.jenkins_username
    jenkins_password       = var.jenkins_password
    jenkins_credentials_id = var.jenkins_credentials_id
  }
}

指令碼解說

#!/bin/bash

JENKINS_URL="${jenkins_url}"
JENKINS_USERNAME="${jenkins_username}"
JENKINS_PASSWORD="${jenkins_password}"

TOKEN=$(curl -u $JENKINS_USERNAME:$JENKINS_PASSWORD '$JENKINS_URL'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb))
INSTANCE_NAME=$(curl -s 169.254.169.254/latest/meta-data/local-hostname)
INSTANCE_IP=$(curl -s 169.254.169.254/latest/meta-data/local-ipv4)
JENKINS_CREDENTIALS_ID="${jenkins_credentials_id}"

curl -v -u $JENKINS_USERNAME:$JENKINS_PASSWORD -H "$TOKEN" -d 'script=
import hudson.model.Node.Mode
import hudson.slaves.*
import jenkins.model.Jenkins
import hudson.plugins.sshslaves.SSHLauncher

DumbSlave dumb = new DumbSlave("'$INSTANCE_NAME'", "'$INSTANCE_NAME'", "/home/ec2-user", "3", Mode.NORMAL, "workers", new SSHLauncher("'$INSTANCE_IP'", 22, "'$JENKINS_CREDENTIALS_ID'"), RetentionStrategy.INSTANCE)
Jenkins.instance.addNode(dumb)
' $JENKINS_URL/script

#### 內容解密:

此指令碼首先從 Jenkins 主節點取得有效的 token,然後從 EC2 後設資料中取得例項的私有 IP 地址和主機名。接著,它向 Jenkins 主節點傳送一個帶有 Groovy 指令碼的 GET 請求,以將當前例項新增為 Jenkins 工作節點。該指令碼組態了三個執行器,可以平行執行,並指定了一個工作目錄 /home/ec2-user。此組態確保了構建任務可以在工作節點上執行,而不會影響主節點的效能。

此圖示呈現了 Jenkins 工作節點加入叢集的流程: 圖表翻譯: 此圖表顯示了 Jenkins 工作節點加入叢集的過程。首先,工作節點啟動並執行使用者資料指令碼。然後,指令碼取得 Jenkins 主節點的 token,並從 EC2 後設資料中取得例項的私有 IP 地址和主機名。最後,指令碼將例項新增為 Jenkins 工作節點,從而實作了工作節點的自動加入和叢集的動態擴充套件。

動態自動擴充套件 Jenkins 工作節點池

我們已經定義了一個名為 workers 的標籤,因此每個工作節點例項將在該標籤下加入 Jenkins 叢集。因此,您可以組態構建作業僅在工作節點的機器上執行。

接下來,在 variable.tf 檔案中定義 Jenkins 主節點憑證和工作節點例項型別作為變數。表 5.4 列出了這些變數。

表 5.4 Jenkins 工作節點的 Terraform 變數

變數名稱型別描述
jenkins_usernameStringNoneJenkins 管理員使用者名稱
jenkins_passwordStringNoneJenkins 管理員密碼
jenkins_credentials_idStringNoneJenkins 工作節點根據 SSH 的憑證 ID
jenkins_worker_instance_typeStringt2.mediumJenkins 工作節點 EC2 例項型別

使用 Amazon EC2 Spot 例項或 Savings Plans 節省成本

您可以使用 Amazon EC2 Spot 例項(http://aws.amazon.com/ec2/spot)或訂閱 Amazon Savings Plans(https://aws.amazon.com/savingsplans/)來顯著降低 Jenkins 工作節點的成本(最多可節省 90% 的成本)。

最後,執行 terraform apply 以佈署 Jenkins 工作節點。

自動擴充套件群組

現在,Jenkins 工作節點的藍圖已在啟動組態中定義,我們可以佈署一個自動擴充套件群組來根據啟動組態佈署相似的 Jenkins 工作節點。

jenkins_workers.tf 檔案中使用 aws_autoscaling_group 資源建立自動擴充套件群組:

resource "aws_autoscaling_group" "jenkins_workers" {
  name                 = "jenkins_workers_asg"
  launch_configuration = aws_launch_configuration.jenkins_workers_launch_conf.name
  vpc_zone_identifier  = [for subnet in aws_subnet.private_subnets : subnet.id]
  min_size             = 2
  max_size             = 10
  depends_on           = [aws_instance.jenkins_master, aws_elb.jenkins_elb]

  lifecycle {
    create_before_destroy = true
  }

  tag {
    key                 = "Name"
    value               = "jenkins_worker"
    propagate_at_launch = true
  }

  tag {
    key                 = "Author"
    value               = var.author
    propagate_at_launch = true
  }
}

內容解密:

此自動擴充套件群組將執行 2 到 10 個工作節點(預設為初始啟動的 2 個),每個工作節點都帶有名稱 jenkins_worker 的標籤。自動擴充套件群組使用參考來填寫啟動組態名稱。

  • depends_on 用於確保 Jenkins 主節點例項在佈署工作節點之前正在執行,因為工作節點需要 Jenkins 主節點 IP 才能成功加入叢集。
  • 由於啟動組態是不可變的,因此無法在建立後對其進行修改(例如,升級 Jenkins 工作節點例項型別或更改基礎 AMI)。因此,需要銷毀啟動組態並建立一個新的啟動組態;這就是使用 create_before_destroy 生命週期設定的原因。

自動擴充套件策略

到目前為止,工作節點的數量是靜態和固定的。為了動態擴充套件工作節點的數量,我們將根據 CPU 利用率定義擴充套件策略。這提供了額外的容量來處理額外的作業構建,而無需維護過多的空閒 Jenkins 工作節點並支付額外的費用。

建立一個 cloudwatch.tf 檔案,並定義一個根據 CPU 利用率的 AWS CloudWatch 指標警示。當平均 CPU 利用率在 2 分鐘內超過 80% 時,CloudWatch 警示將觸發擴充套件事件以新增新的 Jenkins 工作節點例項,如下所示:

resource "aws_cloudwatch_metric_alarm" "high-cpu-jenkins-workers-alarm" {
  alarm_name          = "high-cpu-jenkins-workers-alarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "80"

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.jenkins_workers.name
  }

  alarm_description = "This metric monitors workers cpu utilization"
  alarm_actions     = [aws_autoscaling_policy.scale-out.arn]
}

resource "aws_autoscaling_policy" "scale-out" {
  name                   = "scale-out-jenkins-workers"
  scaling_adjustment    = 1
  adjustment_type       = "ChangeInCapacity"
  cooldown              = 300
  autoscaling_group_name = aws_autoscaling_group.jenkins_workers.name
}

內容解密:

  • 當平均 CPU 利用率超過 80% 時,觸發擴充套件事件以新增新的 Jenkins 工作節點例項。
  • cooldown 設定為 300 秒,以確保自動擴充套件群組在前一個擴充套件活動生效之前不會啟動或終止額外的 Jenkins 工作節點。

同樣,我們定義另一個 CloudWatch 警示,當平均 CPU 利用率在 2 分鐘內低於 20% 時,觸發縮減事件以刪除 Jenkins 工作節點:

resource "aws_cloudwatch_metric_alarm" "low-cpu-jenkins-workers-alarm" {
  alarm_name          = "low-cpu-jenkins-workers-alarm"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "20"

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.jenkins_workers.name
  }

  alarm_description = "This metric monitors ec2 cpu utilization"
  alarm_actions     = [aws_autoscaling_policy.scale-in.arn]
}

resource "aws_autoscaling_policy" "scale-in" {
  name                   = "scale-in-jenkins-workers"
  scaling_adjustment    = -1
  adjustment_type       = "ChangeInCapacity"
  cooldown              = 300
  autoscaling_group_name = aws_autoscaling_group.jenkins_workers.name
}

圖表翻譯:

此圖示顯示了自動擴充套件群組的擴充套件策略。當 CPU 利用率超過門檻值時,自動擴充套件群組將新增新的工作節點。當 CPU 利用率低於門檻值時,自動擴充套件群組將刪除工作節點。

@startuml
skinparam backgroundColor #FEFEFE

title Terraform 自動擴充套件 Jenkins 工作節點池

|開發者|
start
:提交程式碼;
:推送到 Git;

|CI 系統|
:觸發建置;
:執行單元測試;
:程式碼品質檢查;

if (測試通過?) then (是)
    :建置容器映像;
    :推送到 Registry;
else (否)
    :通知開發者;
    stop
endif

|CD 系統|
:部署到測試環境;
:執行整合測試;

if (驗證通過?) then (是)
    :部署到生產環境;
    :健康檢查;
    :完成部署;
else (否)
    :回滾變更;
endif

stop

@enduml

如果您執行 terraform apply 命令,您將看到 Terraform 要建立兩個 CloudWatch 警示。接下來,我們將執行 Stress 工具有效測試工作節點自動擴充套件群組的擴充套件策略。