はじめに
- 今回はAmazon EC2でJenkinsサーバを構築し、Jenkins上でTerraformによるインフラのデプロイをやってみたいと思います。
- 以下のバージョンで動作確認をしています。
- Jenkins: 2.361.1
- Terraform: 1.3.4
注意事項
- 本記事で構築するアーキテクチャはdemo用のため、本番環境などで利用する際に非機能要件等を考慮した適切な設計が必要です。
- ソースのバージョン管理としてGithubを使用していますが、Githubアカウントをお持ちでない方は別途用意する必要があります。
- 本記事はJenkinsパイプラインの構築およびJenkinsパイプラインを使ったTerraformデプロイのやり方にフォーカスしていますので、それを実現するための関連AWSリソースの詳細構築手順を割愛させていただきます。
構成図
構成図の説明
- Terraformコードをリモートリポジトリにpush
- Jenkinsコンソールにブラウザ接続し、ジョブを実行
- TerraformコードをリモートリポジトリからJenkinsサーバにclone
- terraform planとterraform applyを実行、tfstateファイルは同じアカウントに作成したS3バケットに保存
- Terraformで検証用AWSリソースを作成
環境構築
上記のアーキテクチャを実現するためのAWSリソースをap-northeast-1リージョンに作成していきます。記載ない設定項目はデフォルト値を使用します。(詳細構築手順は割愛)
また、ネットワーク系(VPC、Subnet等)リソースは既存のリソースを使っていただいても構いません。
※以下のCloudFormationテンプレートを用意していますので、構築の手間を省きたいという方はご活用いただければと思います。
cfn template
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
MyPublicIP:
Type: "String"
Description: "end with /32"
Default: ""
KeyName:
Type: "AWS::EC2::KeyPair::KeyName"
InstanceType:
Type: "String"
Default: "t2.micro"
AMI:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
TfstateS3BucketName:
Type: "String"
Default: ""
Resources:
# Create VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: "Name"
Value: "tf-jenkins-demo-vpc"
# Create IGW
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: "Name"
Value: "tf-jenkins-demo-igw"
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Create Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: "tf-jenkins-demo-public-rtb"
VpcId: !Ref VPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
RouteTableId: !Ref PublicRouteTable
# Create Public Subnet
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: !Sub ${AWS::Region}a
Tags:
- Key: "Name"
Value: "tf-jenkins-demo-public-subnet-1a"
PublicSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnetA
# Create SG for EC2
LinuxEC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "tf-jenkins-demo-linux-sg"
GroupDescription: "tf-jenkins-demo-linux-sg"
VpcId: !Ref VPC
Tags:
- Key: Name
Value: "tf-jenkins-demo-linux-sg"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: !Ref MyPublicIP
Description: "for jenkins console"
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref MyPublicIP
Description: "for ssh"
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: "0.0.0.0/0"
Description: "for internet access"
# Create IAM Role for EC2
IAMRoleForLinuxEC2:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: "ec2-s3-full-access-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- 'ec2:*'
- 's3:*'
Resource: '*'
RoleName: "ec2-s3-full-access-role"
LinuxInstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Path: /
Roles:
- !Ref IAMRoleForLinuxEC2
# Create EC2
LinuxEC2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AMI
KeyName: !Ref KeyName
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref LinuxInstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: !Ref PublicSubnetA
GroupSet:
- !Ref LinuxEC2SG
Tags:
- Key: Name
Value: "jenkins-server"
# Create S3 Bucket for saving tfstate file
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref TfstateS3BucketName
# Public ip of jenkins server
Outputs:
JenkinsServerPublicIp:
Description: Public IP Of Jenkins Server
Value: !GetAtt LinuxEC2.PublicIp
Export:
Name: !Sub "${AWS::StackName}-PublicIp"
ネットワーク
- VPC
設定項目 | 設定値 |
---|---|
名前タグ | tf-jenkins-demo-vpc |
IPv4 CIDRブロック | 10.0.0.0/16 |
DNSホスト名 | 有効化 |
DNS解決 | 有効化 |
- インターネットゲートウェイ
設定項目 | 設定値 |
---|---|
名前タグ | tf-jenkins-demo-igw |
アタッチするVPC | tf-jenkins-demo-vpc |
- ルートテーブル
設定項目 | 設定値 |
---|---|
名前タグ | tf-jenkins-demo-public-rtb |
VPC | tf-jenkins-demo-vpc |
ルート | 送信先: 0.0.0.0/0 ターゲット: tf-jenkins-demo-igw |
- サブネット
設定項目 | 設定値 |
---|---|
VPC | tf-jenkins-demo-vpc |
サブネット名 | tf-jenkins-demo-public-subnet-1a |
アベイラビリティーゾーン | ap-northeast-1a |
IPv4 CIDRブロック | 10.0.1.0/24 |
関連付けるルートテーブル | tf-jenkins-demo-public-rtb |
セキュリティグループ
設定項目 | 設定値 |
---|---|
セキュリティグループ名 | tf-jenkins-demo-linux-sg |
説明 | tf-jenkins-demo-linux-sg |
VPC | tf-jenkins-demo-vpc |
インバウンドルール(カスタムTCP) | タイプ: タスタムTCP プロトコル: TCP ポート範囲: 8080 ソース: 自分自身のパブリックIPアドレス(末尾に/32が必要) |
インバウンドルール(SSH) | タイプ: SSH プロトコル: TCP ポート範囲: 22 ソース: 自分自身のパブリックIPアドレス(末尾に/32が必要) |
アウトバウンドルール(インターネット接続) | タイプ: すべてのトラフィック プロトコル: すべて ポート範囲: すべて 送信先: 0.0.0.0/0 |
IAMポリシー&ロール
- IAMポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:*",
"s3:*"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
- IAMロール
設定項目 | 設定値 |
---|---|
ロール名 | ec2-s3-full-access-role |
信頼されたエンティティタイプ | EC2 |
許可ポリシー | ec2-s3-full-access-policy |
EC2インスタンス
設定項目 | 設定値 |
---|---|
名前タグ | jenkins-server |
AMI | Amazon Linux 2(amzn2-ami-hvm-x86_64-gp2) |
インスタンスタイプ | t2.micro |
キーペア | 既存のものを選択、または新規作成 |
VPC | tf-jenkins-demo-vpc |
サブネット | tf-jenkins-demo-public-subnet-1a |
パブリック IP の自動割り当て | 有効化 |
セキュリティグループ | tf-jenkins-demo-linux-sg |
IAMインスタンスプロフィール | ec2-s3-full-access-role |
S3バケット(Terraformのbackend用)
設定項目 | 設定値 |
---|---|
バケット名 | 任意(グローバルで一意でなければならない |
リージョン | ap-northeast-1 |
Jenkinsの構築
Jenkinsのインストール
上記作成したEC2にJenkinsをインストールしていきます。Dockerコンテナなどで構築する方法もあるかと思いますが、今回はEC2に直接インストールすることにします。
EC2にssh接続して、Jenkins公式サイトのインストール手順を参考に、下記のコマンドを実行していきます。
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum upgrade -y
sudo amazon-linux-extras install java-openjdk11 -y
sudo yum install git -y
# Jenkinsのバージョンを指定してインストールする
sudo yum install jenkins-2.361.1 -y
sudo systemctl daemon-reload
sudo systemctl enable jenkins
sudo systemctl start jenkins
Jenkinsの初期設定
ブラウザからJenkinsにアクセスして初期設定を行います。
URL: http://<EC2のパブリックIPアドレス>:8080/
初回アクセス時は、Unlock Jenkinsの画面が表示され、Administrator passwordが聞かれます。EC2で以下のコマンドを実行してAdministrator passwordを確認します。
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
「Administrator password」を入力し、「Continue」をクリックします。次のプラグインのインストール確認画面で「Install suggested plugins」を選択します。
初期Adminユーザ作成画面が表示されたら必要事項を入力して「Save and Continue」をクリックします。
そのまま「Save and Continue」をクリックして、「Start using Jenkins」をクリックします。
Terraformプラグインのインストール
ダッシュボードが表示されたら、「Jenkinsの管理」→「プラグインの管理」を開きます。「Plugin Manager」画面で、「利用可能」タブをクリックし、「Terraform」と検索し、表示されたプラグインをインストールします。
インストール完了後、「Jenkinsの管理」→「Global Tool Configuration」を開き、Terraform欄の「Terraform追加」をクリックします。「Name」を入力し該当バージョンのTerraformを選択します。今回は1.3.4をインストールします。最後に「Save」をクリックして設定を保存します。
※「Name」欄に入力する名前は任意ですが、Jenkinsfileのtoolsブロックに記述する名前と一致する必要があります。
Githubリポジトリの作成
今回はdemoのため、パブリックリポジトリを作成することにします。プライベートリポジトリを作成する場合は、リポジトリcloneのためのaccess tokenやssh keyをJenkinsに登録する必要がありますので、ご注意ください。
JenkinsfileとTerraformコードの作成
Jenkinsfile
pipelineのtoolsブロックにJenkinsにインストールしたTerraformプラグインの「Name」を記述します。また、今回はinputを使って、terraform plan実行後、手動で承認することでterraform applyを実行するようにします。
※本スクリプトはdemo用のため、基本的な処理しか記述されていません。
pipeline {
agent any
options {
skipDefaultCheckout(true)
}
tools {
terraform "terraform-1.3.4"
}
stages{
stage("clean workspace") {
steps {
cleanWs()
}
}
stage("checkout") {
steps {
checkout scm
}
}
stage("terraform init") {
steps {
sh "terraform init -no-color"
}
}
stage("terraform plan") {
steps {
sh "terraform plan -no-color -out=plan.out"
input message: "Apply Plan?", ok: "Apply"
}
}
stage("terraform apply") {
steps {
sh "terraform apply plan.out -no-color"
}
}
}
}
Terraformコード
今回はdemoのため、tfファイルを分けずに、provider、backend、対象リソース等を全部main.tfに記述します。今回はTerraformでVPCを作成してみます。「created-by-terraform」という名前タグをつけます。
backendに使用するS3バケットは環境構築で作成したS3バケットの名前を指定してください。
provider "aws" {
region = "ap-northeast-1"
}
terraform {
required_version = "~> 1.3.4"
backend "s3" {
# your own s3 bucket
bucket = ""
region = "ap-northeast-1"
key = "terraform.tfstate"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "created-by-terraform"
}
}
ディレクトリ構成
作成済みのJenkinsfileとTerraformコードを事前に用意したGithubリポジトリにpushします。ディレクトリ構成は以下とします。
.
├── .gitignore
├── Jenkinsfile
└── main.tf
Jenkinsジョブの作成
ダッシュボード→「新規ジョブ作成」をクリックします。ジョブ名を入力し、パイプラインを選択し、「OK」をクリックします。
パイプラインを以下のように設定します。
- 定義: Pipeline script from SCM
- SCM: Git
- リポジトリURL: 該当リポジトリのURL
- ブランチ指定子: 対象ブランチを指定
- Script Path: Jenkinsfile
Jenkinsジョブの実行
では、ジョブを実行してみます。「ビルド実行」をクリックします。
ビルド履歴に実行中のジョブが表示されますので、該当ジョブの「Console Output」画面を開きます。
ジョブの中でterraform initとterraform planが実行され、その後「Apply Plan?」というinput messageが表示されます。「Apply」をクリックするとterraform applyが実行され、AWSリソースが作成されます。「Abort」をクリックするとジョブが終了しますので、terraform applyは実行されません。
ジョブが正常に終了したら、AWSマネージメントコンソールで該当リソースを確認してみましょう。「created-by-terraform」というVPCが無事作成されましたね。
今回はJenkinsfileにinputを記述し、手動承認のフェーズを入れていますが、手動承認が必要ない場合は、該当箇所のinputを削除していただいても構いません。
また、Jenkinsジョブのビルドトリガを設定することで、SCMポーリングして変更があったらジョブを起動したり、あるいは定期的にジョブを実行することもできます。
おわりに
今回はAmazon EC2でJenkinsサーバを構築し、Jenkins上でTerraformによるインフラのデプロイをやってみました。簡単なdemoではありますが、JenkinsでTerraformを動かす際にぜひ参考にしていただければと思います。
最後に、今回構築したAWSリソースの削除をお忘れずに!