返回文章列表

Packer 建立 Jenkins AMI 與 Terraform 佈署

本文介紹如何使用 Packer 建立 Jenkins 工作節點的 AMI,並使用 Terraform 將 Jenkins 佈署到 AWS。文章涵蓋 Packer 範本的撰寫、setup.sh 指令碼的組態、Terraform 基礎設施的定義和自動化 AMI 建立流程。同時也探討了使用 AWS Auto Scaling

DevOps 雲端計算

Packer 是一款用於建立機器映像的工具,它可以自動化映像的構建過程,並將其釋出到不同的雲平台。本文將使用 Packer 建立 Jenkins 工作節點的 AMI,並使用 Terraform 將其佈署到 AWS。首先,我們需要準備一個 Packer 範本檔案,其中定義了構建 AMI 的步驟,包括選擇基礎 AMI、安裝必要的軟體(例如 JDK、Git、Docker)以及組態 Jenkins 環境。接著,我們會使用 shell provisioner 執行 setup.sh 指令碼,該指令碼負責安裝和組態 Docker、Java 等必要的軟體,並將 ec2-user 加入 docker 群組。完成 AMI 的構建後,我們將使用 Terraform 來管理 AWS 上的基礎設施,包括 VPC、子網路、安全組、EC2 例項等。Terraform 的組態檔案以宣告式的語法描述了所需的基礎設施狀態,可以確保基礎設施的一致性和可重複性。最後,我們將探討如何利用 AWS Auto Scaling 功能,根據負載情況自動調整 Jenkins 工作節點的數量,以提高資源利用率並降低成本。

使用 Packer 製作 Jenkins 機器映像檔

在前面的章節中,我們已經完成了 Jenkins 主機的設定,包括安裝必要的外掛和組態 Jenkins 的初始化設定。接下來,我們將使用 HashiCorp Packer 來建立 Jenkins 工作節點(worker)的 Amazon Machine Image(AMI)。

Jenkins 工作節點 AMI 的建立過程

建立 Jenkins 工作節點 AMI 的過程相對簡單,如下所示。首先,我們需要定義一個 Packer 範本檔案,用於描述如何建立 AMI。

{
  "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": "jenkins-worker",
      "ami_description": "Jenkins worker's AMI"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "script": "./setup.sh",
      "execute_command": "sudo -E -S sh '{{ .Path }}'"
    }
  ]
}

內容解密:

此 Packer 範本檔案定義了建立 Jenkins 工作節點 AMI 的過程。它使用 amazon-ebs 構建器來建立一個根據指定 source_ami 的臨時例項,並使用 shell provisioner 來執行 setup.sh 指令碼,以安裝所需的軟體和組態例項。

setup.sh 指令碼詳解

setup.sh 指令碼用於安裝 JDK、Git、Docker 等必要的軟體,並組態 Docker 服務。

#!/bin/bash
yum remove -y java
yum update -y
yum install -y git docker java-1.8.0-openjdk
usermod -aG docker ec2-user
systemctl enable docker

內容解密:

此指令碼首先移除現有的 Java 版本,然後更新系統並安裝 Git、Docker 和 OpenJDK 1.8。接著,它將 ec2-user 新增到 Docker 組,並啟用 Docker 服務。這些步驟確保了 Jenkins 工作節點具備執行 Dockerized 微服務所需的環境。

自動化 AMI 建立流程

為了實作不可變基礎設施(Immutable Infrastructure)的方法,我們可以使用 Jenkins 自動化 AMI 的建立流程。如圖 4.20 所示,這個流程涉及使用 GitHub 儲存 Packer 範本檔案,並在程式碼推播事件發生時觸發 Jenkins 構建作業。該作業將驗證範本變更,啟動 AMI 製作流程,並根據新的 AMI 建立 EC2 例項。

使用 Terraform 佈署 Jenkins

在前面的章節中,我們已經使用 HashiCorp Packer 建立了自定義的 Jenkins 機器映像檔(AMI)。現在,我們將使用這些 AMI 將 Jenkins 佈署到 AWS 上。為此,我們將使用 HashiCorp Terraform 這一基礎設施即程式碼(IaC)工具來定義和管理我們的基礎設施。

Terraform 簡介

Terraform 是一種流行的 IaC 工具,它允許我們以宣告式的方式定義和管理我們的基礎設施。透過編寫 Terraform 組態檔案,我們可以描述所需的基礎設施狀態,然後使用 Terraform 將其應用到實際的環境中。

使用 Terraform 佈署 Jenkins

首先,我們需要編寫 Terraform 組態檔案,以定義 Jenkins 的基礎設施。這包括建立 VPC、安全組、EC2 例項等資源。然後,我們可以使用 Terraform 命令來應用這些組態,從而在 AWS 上佈署 Jenkins。

# 示例 Terraform 組態檔案
provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "jenkins_master" {
  ami           = "ami-xxxxxx"
  instance_type = "t2.micro"
}

resource "aws_instance" "jenkins_worker" {
  ami           = "ami-yyyyyy"
  instance_type = "t2.micro"
}

圖表翻譯:

此圖示展示了使用 Terraform 將 Jenkins 佈署到 AWS 的流程。首先,我們定義 Terraform 組態檔案,然後使用 Terraform 命令來應用這些組態,從而在 AWS 上建立所需的資源。

自動縮放 Jenkins 工作節點

為了提高資源利用率和降低成本,我們可以使用 AWS Auto Scaling 自動縮放 Jenkins 工作節點。這樣,根據工作負載的變化,Jenkins 工作節點的數量可以動態地增加或減少。

# 示例 Auto Scaling 組態
resource "aws_autoscaling_group" "jenkins_workers" {
  launch_configuration = aws_launch_configuration.jenkins_worker.name
  min_size             = 1
  max_size             = 5
}

resource "aws_launch_configuration" "jenkins_worker" {
  image_id        = "ami-yyyyyy"
  instance_type   = "t2.micro"
}

圖表翻譯:

此圖示展示了使用 AWS Auto Scaling 自動縮放 Jenkins 工作節點的流程。根據工作負載的變化,Auto Scaling 組會動態地調整 Jenkins 工作節點的數量,以確保資源的有效利用。

匯入基礎設施即程式碼(Infrastructure as Code)

基礎設施即程式碼簡介

基礎設施即程式碼(IaC)允許您使用組態檔案來管理基礎設施。這樣可以降低成本、減少風險,並加快在雲端佈署資源的速度。另一個好處是,您的基礎設施變得可測試、可重複、自癒、冪等,最重要的是,易於理解,因為您的基礎設施程式碼本質上就是您的檔案。

有多種 IaC 工具可供選擇,每種都有其自己的實作方式(圖 5.2)。有些工具專注於特定的雲端,例如 AWS CloudFormation、Azure Resource Manager、OpenStack Heat 和 Google Cloud Deployment Manager。其他工具則試圖橋接所有雲端供應商,並遮蔽它們的語義差異,以提供與雲端無關的實作。這類別包括 HashiCorp Terraform、HashiCorp Vagrant、Chef Provisioning 和 Pulumi。

為何選擇 Terraform

在本文中,我們將專注於使用 HashiCorp Terraform 來佈署 Jenkins 元件。Terraform 提供了一個靈活的資源和供應商抽象,是一個平台無關的工具,支援多個 IaaS 供應商,如 AWS、Microsoft Azure、Google Cloud Platform 和 DigitalOcean。此外,Terraform 是開源的,具有簡單統一的語法,沒有陡峭的學習曲線,新使用者可以輕鬆上手,並且有豐富的線上資源可供參考,用於任何基礎設施佈署使用案例。

Terraform 使用方法

Terraform 使用推播方法:開發人員或維運工程師在範本檔案中描述所需的基礎設施,Terraform 直接透過其 API 與雲端供應商互動。例如,如果目標雲端供應商是 AWS,Terraform 使用 Terraform AWS 提供者外掛,該外掛在底層使用 AWS 官方 SDK 來建立/更新或銷毀資源。

為了維護基礎設施的期望狀態並檢測變化,Terraform 生成一個名為 terraform.tfstate 的 JSON 檔案,該檔案儲存了您管理的基礎設施和組態的狀態。Terraform 使用差異技術在任何操作之前檢測變化。因此,個人和團隊可以安全且可預測地更改基礎設施。

在 AWS 上組態 VPC

正如在第 3 章中討論的,我們的 Jenkins 叢集將佈署在私有子網內的 VPC 中(圖 5.4)。我們可以將叢集佈署在 AWS 建立的預設 VPC 中。但是,為了完全控制網路拓撲,我們將從頭開始建立一個 VPC,以將 Jenkins 叢集與我們將在高階章節中佈署的應用程式工作負載隔離開來。以下架構圖總結了目標 VPC 架構:

AWS VPC 組態

Terraform 使用一種名為 HashiCorp 組態語言(HCL)的 DSL,一種宣告式語言來描述基礎設施資源。這些資源被描述在一個簡單的文字檔案中,副檔名為 .tf

我們將採用模組化開發方法,將 Jenkins 叢集佈署分成多個範本檔案,而不是編寫一個大型的範本檔案。每個檔案負責佈署目標基礎設施的一個元件或 AWS 資源。首先,建立一個名為 terraform.tf 的檔案,內容如下:

provider "aws" {
  region                  = var.region
  shared_credentials_file = var.shared_credentials_file
  profile                 = var.aws_profile
}

程式碼解密:

此段程式碼定義了AWS提供者及其組態。

  • region 指定了要使用的AWS區域,這裡使用了變數 var.region
  • shared_credentials_file 指定了AWS憑證檔案的路徑,這裡使用了變數 var.shared_credentials_file
  • profile 指定了要使用的AWS憑證組態檔案的名稱,這裡使用了變數 var.aws_profile

這段程式碼是 Terraform 組態檔案的一部分,用於設定與 AWS 的連線,以便 Terraform 可以管理 AWS 資源。

AWS VPC 架構圖

圖表翻譯: 此圖表展示了AWS VPC的架構,包括網際網路、IGW(網際網路閘道)、VPC、公有子網、私有子網、NAT閘道以及Jenkins例項之間的關係。IGW允許來自網際網路的流量進入VPC,並路由到公有子網。私有子網中的Jenkins例項透過NAT閘道存取網際網路。

使用 Terraform 佈建 AWS VPC

在建立 AWS VPC 時,我們需要在 vpc.tf 檔案中宣告一個 AWS VPC 資源。以下程式碼片段使用 10.0.0.0/16 的 CIDR 區塊作為 VPC,但您可以選擇不同的 CIDR 區塊:

resource "aws_vpc" "default" {
  cidr_block = var.cidr_block
  enable_dns_hostnames = true
  tags = {
    Name = var.vpc_name
    Author = var.author
  }
}

內容解密:

  • resource "aws_vpc" "default":宣告一個名為 default 的 AWS VPC 資源。
  • cidr_block = var.cidr_block:設定 VPC 的 CIDR 區塊,這裡使用變數 var.cidr_block,可以根據需求更改。
  • enable_dns_hostnames = true:啟用 DNS 主機名稱支援。
  • tags:為 VPC 新增標籤,包括 NameAuthor,方便資源管理。

定義 Terraform 變數

Terraform 變數是使用 variable 區塊建立的,它們具有名稱和可選的型別、預設值和描述引數。我們將在 variables.tf 檔案中定義這些變數,如下所示:

variable "region" {
  description = "AWS region"
  type        = string
}

variable "cidr_block" {
  description = "VPC CIDR block"
  default     = "10.0.0.0/16"
}

內容解密:

  • variable "region":定義一個名為 region 的變數,用於指定 AWS 區域。
  • variable "cidr_block":定義一個名為 cidr_block 的變數,用於指定 VPC 的 CIDR 區塊,並設定預設值為 10.0.0.0/16

初始化 Terraform 組態

在執行 Terraform 之前,需要安裝 AWS 外掛程式。執行以下命令來初始化 Terraform 組態:

terraform init

這個命令會安裝 AWS 提供者外掛程式並初始化新的組態。

生成執行計劃

使用以下命令生成變更的執行計劃(乾跑):

terraform plan --var-file="variables.tfvars"

這個命令會顯示目標計劃,用於驗證變更並避免不必要的變更。

套用變更

確認執行計劃正確後,使用以下命令套用變更:

terraform apply --var-file="variables.tfvars"

輸入 yes 以套用變更,Terraform 將建立 AWS VPC 資源。

建立 VPC 子網路

僅建立 VPC 是不夠的,我們還需要建立子網路以便將 Jenkins 例項放置在這個隔離的網路中。建立一個名為 subnets.tf 的檔案,並在其中定義兩個公有子網路和兩個私有子網路,分別位於不同的可用區,以提高彈性。

resource "aws_subnet" "public_subnets" {
  vpc_id = aws_vpc.management.id
  # 其他屬性...
}

內容解密:

  • resource "aws_subnet" "public_subnets":宣告一個名為 public_subnets 的 AWS 子網路資源。
  • vpc_id = aws_vpc.management.id:將子網路關聯到指定的 VPC,使用 Terraform 的內插語法參照 VPC 的 ID。

使用 Terraform 組態 AWS VPC 網路架構

在現代化的雲端基礎設施管理中,自動化和版本控制是不可或缺的關鍵要素。Terraform 提供了一種宣告式的基礎設施即程式碼(Infrastructure as Code, IaC)解決方案,使我們能夠以程式碼的方式定義和管理雲端資源。本文將探討如何使用 Terraform 在 AWS 上建立和管理 VPC(Virtual Private Cloud)網路架構,包括子網路、路由表和 NAT 閘道器的組態。

5.2.1 建立 VPC 和子網路

首先,我們需要在 AWS 上建立一個 VPC 並在其中組態子網路。以下是一個基本的 Terraform 組態範例:

resource "aws_vpc" "management" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "management_vpc"
    Author = var.author
  }
}

resource "aws_subnet" "public_subnets" {
  count             = var.public_subnets_count
  vpc_id            = aws_vpc.management.id
  cidr_block        = "10.0.${count.index * 2 + 1}.0/24"
  availability_zone = element(var.availability_zones, count.index)
  map_public_ip_on_launch = true
  tags = {
    Name = "public_10.0.${count.index * 2 + 1}.0_${element(var.availability_zones, count.index)}"
    Author = var.author
  }
}

resource "aws_subnet" "private_subnets" {
  count             = var.private_subnets_count
  vpc_id            = aws_vpc.management.id
  cidr_block        = "10.0.${count.index * 2}.0/24"
  availability_zone = element(var.availability_zones, count.index)
  map_public_ip_on_launch = false
  tags = {
    Name = "private_10.0.${count.index * 2}.0_${element(var.availability_zones, count.index)}"
    Author = var.author
  }
}

內容解密:

  • aws_vpc 資源用於建立 VPC,並指定其 CIDR 區塊。
  • aws_subnet 資源用於在 VPC 中建立子網路,包括公有子網路和私有子網路。
  • 使用 countelement 函式動態建立多個子網路,並根據索引計算 CIDR 區塊。
  • map_public_ip_on_launch 屬性控制是否自動為子網路中的例項分配公有 IP 地址。

設定變數

variables.tf 檔案中定義必要的變數:

variable "availability_zones" {
  type        = list(string)
  description = "可用區列表"
}

variable "public_subnets_count" {
  type        = number
  default     = 2
  description = "公有子網路數量"
}

variable "private_subnets_count" {
  type        = number
  default     = 2
  description = "私有子網路數量"
}

variable "author" {
  type        = string
  description = "資源建立者"
}

圖表翻譯:

此圖示呈現了 VPC 和子網路的架構: 此圖表顯示了 VPC 與其內部公有和私有子網路的關係。

建立路由表

為了控制 VPC 中的流量路由,我們需要建立公有和私有路由表。

公有路由表

首先,建立一個 public_rt.tf 檔案,並定義 Internet 閘道器和公有路由表:

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.management.id
  tags = {
    Name = "igw_${var.vpc_name}"
    Author = var.author
  }
}

resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.management.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
  tags = {
    Name = "public_rt_${var.vpc_name}"
    Author = var.author
  }
}

resource "aws_route_table_association" "public" {
  count          = var.public_subnets_count
  subnet_id      = element(aws_subnet.public_subnets.*.id, count.index)
  route_table_id = aws_route_table.public_rt.id
}

私有路由表

接著,建立一個 private_rt.tf 檔案,並定義 NAT 閘道器和私有路由表:

resource "aws_eip" "nat" {
  vpc = true
  tags = {
    Name = "eip-nat_${var.vpc_name}"
    Author = var.author
  }
}

resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = element(aws_subnet.public_subnets.*.id, 0)
  tags = {
    Name = "nat_${var.vpc_name}"
    Author = var.author
  }
}

resource "aws_route_table" "private_rt" {
  vpc_id = aws_vpc.management.id
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat.id
  }
  tags = {
    Name = "private_rt_${var.vpc_name}"
    Author = var.author
  }
}

resource "aws_route_table_association" "private" {
  count          = var.private_subnets_count
  subnet_id      = element(aws_subnet.private_subnets.*.id, count.index)
  route_table_id = aws_route_table.private_rt.id
}

圖表翻譯:

此圖示展示了公有和私有路由表的組態:

@startuml
skinparam backgroundColor #FEFEFE

title Packer 建立 Jenkins AMI 與 Terraform 佈署

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

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

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

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

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

stop

@enduml

此圖表說明瞭公有和私有路由表的流量走向。