6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[AWS]JenkinsでTerraformによるインフラデプロイをやってみた

Last updated at Posted at 2022-11-11

はじめに

  • 今回はAmazon EC2でJenkinsサーバを構築し、Jenkins上でTerraformによるインフラのデプロイをやってみたいと思います。
  • 以下のバージョンで動作確認をしています。
    • Jenkins: 2.361.1
    • Terraform: 1.3.4

注意事項

  • 本記事で構築するアーキテクチャはdemo用のため、本番環境などで利用する際に非機能要件等を考慮した適切な設計が必要です。
  • ソースのバージョン管理としてGithubを使用していますが、Githubアカウントをお持ちでない方は別途用意する必要があります。
  • 本記事はJenkinsパイプラインの構築およびJenkinsパイプラインを使ったTerraformデプロイのやり方にフォーカスしていますので、それを実現するための関連AWSリソースの詳細構築手順を割愛させていただきます。

構成図

tf-jenkins-demo.drawio.png

構成図の説明

  1. Terraformコードをリモートリポジトリにpush
  2. Jenkinsコンソールにブラウザ接続し、ジョブを実行
  3. TerraformコードをリモートリポジトリからJenkinsサーバにclone
  4. terraform planとterraform applyを実行、tfstateファイルは同じアカウントに作成したS3バケットに保存
  5. 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ポリシー
ec2-s3-full-access-policy
{
    "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/
image.png
初回アクセス時は、Unlock Jenkinsの画面が表示され、Administrator passwordが聞かれます。EC2で以下のコマンドを実行してAdministrator passwordを確認します。

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

「Administrator password」を入力し、「Continue」をクリックします。次のプラグインのインストール確認画面で「Install suggested plugins」を選択します。
Screenshot 2022-11-10 at 11.21.14.png

初期Adminユーザ作成画面が表示されたら必要事項を入力して「Save and Continue」をクリックします。

Screenshot 2022-11-10 at 11.30.49.png

そのまま「Save and Continue」をクリックして、「Start using Jenkins」をクリックします。
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323935343733342f32353661323764632d353765332d393262302d376532302d3764373130323437393062622e706e67.png
Screenshot 2022-11-10 at 11.32.23.png

Terraformプラグインのインストール

ダッシュボードが表示されたら、「Jenkinsの管理」→「プラグインの管理」を開きます。「Plugin Manager」画面で、「利用可能」タブをクリックし、「Terraform」と検索し、表示されたプラグインをインストールします。
Screenshot 2022-11-10 at 11.53.38.png
インストール完了後、「Jenkinsの管理」→「Global Tool Configuration」を開き、Terraform欄の「Terraform追加」をクリックします。「Name」を入力し該当バージョンのTerraformを選択します。今回は1.3.4をインストールします。最後に「Save」をクリックして設定を保存します。
※「Name」欄に入力する名前は任意ですが、Jenkinsfileのtoolsブロックに記述する名前と一致する必要があります。
Screenshot 2022-11-10 at 12.12.35.png

Githubリポジトリの作成

今回はdemoのため、パブリックリポジトリを作成することにします。プライベートリポジトリを作成する場合は、リポジトリcloneのためのaccess tokenやssh keyをJenkinsに登録する必要がありますので、ご注意ください。

JenkinsfileとTerraformコードの作成

Jenkinsfile

pipelineのtoolsブロックにJenkinsにインストールしたTerraformプラグインの「Name」を記述します。また、今回はinputを使って、terraform plan実行後、手動で承認することでterraform applyを実行するようにします。
※本スクリプトはdemo用のため、基本的な処理しか記述されていません。

Jenkinsfile
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バケットの名前を指定してください。

main.tf
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」をクリックします。
Screenshot 2022-11-10 at 18.46.44.png

パイプラインを以下のように設定します。

  • 定義: Pipeline script from SCM
  • SCM: Git
  • リポジトリURL: 該当リポジトリのURL
    • Screenshot 2022-11-10 at 15.02.08.png
  • ブランチ指定子: 対象ブランチを指定
  • Script Path: Jenkinsfile

Screenshot 2022-11-10 at 18.47.46.png
Screenshot 2022-11-10 at 18.47.56.png
最後に「保存」をクリックします。

Jenkinsジョブの実行

では、ジョブを実行してみます。「ビルド実行」をクリックします。
Screenshot 2022-11-10 at 18.48.09.png
ビルド履歴に実行中のジョブが表示されますので、該当ジョブの「Console Output」画面を開きます。
Screenshot 2022-11-10 at 18.48.24.png
ジョブの中でterraform initとterraform planが実行され、その後「Apply Plan?」というinput messageが表示されます。「Apply」をクリックするとterraform applyが実行され、AWSリソースが作成されます。「Abort」をクリックするとジョブが終了しますので、terraform applyは実行されません。
Screenshot 2022-11-10 at 18.48.43.png

ジョブが正常に終了したら、AWSマネージメントコンソールで該当リソースを確認してみましょう。「created-by-terraform」というVPCが無事作成されましたね。
Screenshot 2022-11-10 at 18.49.52.png

今回はJenkinsfileにinputを記述し、手動承認のフェーズを入れていますが、手動承認が必要ない場合は、該当箇所のinputを削除していただいても構いません。

また、Jenkinsジョブのビルドトリガを設定することで、SCMポーリングして変更があったらジョブを起動したり、あるいは定期的にジョブを実行することもできます。
Screenshot 2022-11-10 at 18.53.48.png

おわりに

今回はAmazon EC2でJenkinsサーバを構築し、Jenkins上でTerraformによるインフラのデプロイをやってみました。簡単なdemoではありますが、JenkinsでTerraformを動かす際にぜひ参考にしていただければと思います。
最後に、今回構築したAWSリソースの削除をお忘れずに!

参考

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?