Jenkins Server環境をCloudFormationで作成

##環境
macOS Big Sur バージョン１１.２.２

##構成図

![jenkins構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650745/a0d89212-4ceb-0d87-74aa-01af6e44467e.png "jenkins構成図")

##今回やること

上記図の左側にあるEC2A（Jenkins Server）の環境をCloudFormationで作成します。
（一度、手動で作成してみることをお勧めします）

この構成図の全体の作成の流れについては別記事を参照してください

https://qiita.com/wanidrums/private/eb761070b8a595241055


##簡単に作成するリソースの説明

・VPC
・IGW（インターネットゲートウェイ）
・VPCGatewayAttachment（VPCとIGWの接続）
・パブリックサブネット
・RouteTable
・SubnetRouteTableAssociation（ルートテーブルにパブリックサブネットの紐付け）
・Route（ルートテーブルとIGWの紐付け）

・EC2（ユーザーデータで　Jenkins,Ansibleのインストール）
・セキュリティグループ
・IamUser（Jenkinsユーザーの作成）

それでは早速見ていきましょう！

##Parameters

```
AWSTemplateFormatVersion: 2010-09-09

Parameters: 
  StackEnv:
    Description: Stack Environment
    Type: String
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: AWS::EC2::KeyPair::KeyName
```

解説：
パラメータの設定は自由ですが、個人で判断して必要なら作成してください。
使いこなすと、より容易にセキュアにテンプレートが作成できます。

【StackEnv】
スタック作成時に環境（Dev、Staging、Prov）を指定します。

【LatestAmiId】
AWS固有のSSMパラメータで、最初からAWSで設定してあるSSMパラメータを使用しています。
デフォルトで指定しているのは最新のAMIのEC2です。

【KeyName】
スタック作成時にEC2のキーペア名を指定します。


##Resources

```
Resources: 
  myVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-vpc"

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-igw"

  VPCAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref myVPC
      InternetGatewayId: !Ref IGW

  PublicSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      VpcId: !Ref myVPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-public-subnet1a"

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref myVPC
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-routetable"

  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1a
      RouteTableId: !Ref RouteTable

  Route:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
```

解説：
「簡単に作成するリソースの説明」で述べた通りに作成してます。
実際に手動で作成したらそれぞれの繋がりがよくわかると思います。
特に難しいことはやっておらず、必要最低限の設定なのでサブネットも一つだけです。

・タグを付けられるものに関しては、Parameterで設定しておいたStackEnvの値を変数として定義しています。
・!Ref関数を使えばテンプレ内のリソースを論理名（myVPCなど）を使用して使いまわせるので便利です。
・論理名の名前は好きな名前でOKです。ただし、わかりやすい名前にしましょう。

##EC2

```
  EC2a:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: !Ref PublicSubnet1a
      AvailabilityZone: ap-northeast-1a
      KeyName: !Ref KeyName
      ImageId: !Ref LatestAmiId
      InstanceType: t2.medium
      Monitoring: false
      SecurityGroupIds:
        - !Ref SecurityGroup
      UserData: !Base64 |
        #!/bin/bash -ex
        sudo yum update -y
        sudo amazon-linux-extras install -y ansible2
        sudo yum install -y java-1.8.0-openjdk-devel.x86_64
        sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.repo
        sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
        sudo yum install -y jenkins git
        sudo systemctl start jenkins
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-ec2a"
```

解説：
【InstanceType】
t2.microだとJenkinsでAnsibleなど色々構築する際にスペックが低すぎてサーバーが落ちることがあるそうです。
t2.mediumか低くてもt2.small以上が無難らしいです。

【UserData】
ここでEC2起動時に記載したコマンドが発動します。

見ての通り、yum updateから
・ansible
・Jenkinsのインストールに必要なjava
・Jenkinsをインストールするためのリポジトリのダウンロード
・Jenkinsのインストール
・gitと連携するためにgitのインストール
・Jenkinsの起動

上記の操作をコマンドで打ち込んでいます。
手動でやる手間が省けるので、便利です。

＊UserDataのコードがうまく機能してなかったら、下記のコードでEC2立ち上げた時のログが見れます。
`sudo cat /var/log/cloud-init-output.log`

##SecurityGroup

```
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 for jenkins
      VpcId: !Ref myVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: for http
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
          Description: for ssh
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          CidrIp: 0.0.0.0/0
          Description: for jenkins
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-ec2sg"
```

解説：
SecurityGroupIngressがインバウンド、SecurityGroupEgressがアウトバウンドです。

HTTP ８０
SSH ２２
Jenkins ８０８０

それぞれのポートをオープンにしています。

・ `IpProtocol: -1`
全てのプロトコルを指定しています。
つまりここではアウトバウンドはフルオープンと言うことですね。

##IamUser

```
  JenkinsUser:
    Type: AWS::IAM::User
    Properties:
      LoginProfile:
        Password: '{{resolve:ssm-secure:test2:1}}'
      Policies:
        - PolicyName: AdministratorAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
                - '*'
              Resource:
                - '*'
      Tags:
        - Key: Name
          Value: !Sub "${StackEnv}-jenkinsuser"
      UserName: jenkins-from-cfn
```

解説：
IamUserの作成です。

＊本来なら必要な権限だけ与えるべきですが、ここではとりあえずサーバーを作って動作させることが目的だったため、フル権限を付与してます。

ここでは`jenkins-from-cfn`というUSERNAMEにしています。
Passwordに関しては、AWS Systems ManagerのSSM Secure Stringパラメータを使用しています。

このパラメータの使用方法として、
１、まずはAWS Systems Managerでパラメータを作成する必要があります。
（コンソールからでもCLIからでも作れます）

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-create-console.html

２、作成したパラメータの名前とバージョンをテンプレに代入して貼り付けるだけです。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references.html

＊パラメータの種類には
・SSMパラメータ（String型、StringList型）
・SSM String Secureパラメータ
があります。

今回僕が使用したのはSSM String Secureパラメータなので、
`'{{resolve:ssm-secure:parameter-name:version}}'`
に当てはめてコードを記述しています。

##参考にした記事

https://dev.classmethod.jp/articles/aws-cloudformation-support-dynamic-references-securestring/


https://dev.classmethod.jp/articles/automatically-generate-a-password-with-cloudformation/