GitLab on Amazon EC2な構成で始めるTerraform × Ansibleハンズオン

## はじめに  
TerraformとAnsibleを使ってAWSにGitLabを構築してみました。これからTerraform/Ansibleを始めよう思っている方の参考になれば幸いです。前提条件として、AWSのアカウントを持っていてEC2・IAM・VPC等の基本的なAWSリソースの使い方を理解していればスムーズに進めれるかと思います。
## 目次
- 実行環境
- 実際のコード
- 実行のイメージ
- 環境構築
- Terraformを動かしてみる
- Terraformで実践的なインフラリソースを構築してみる
- Ansibleを動かしてみる
- AnsibleでGitLabを構築する
- リソースのクリーンアップ
- 参考資料

## 実行環境
    macOS Mojave v10.14.5
    Terraform v0.11.14
    ansible v2.9.2
    virtualenv v16.6.2
    pip3 v19.1.1
    Amazon Linux 2 t2.medium(GitLab環境)

## 実際のコード
[GitHub](https://github.com/takapon564/iac-sample-on-aws)に置いておきます。参考までに。
## 実行のイメージ
実行のイメージです。ローカルPCにTerraformとAnsibleをインストールしています。TerraformでEC2・VPC等のリソースを構築し、AnsibleでGitLabとそれに必要なモジュールをインストールしています。
<img width="607" alt="image.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286861/af98e4df-e7ff-848a-0f81-ded7065d5f3b.jpeg">

## 環境構築 
### Terraformのインストール
[公式サイト](https://learn.hashicorp.com/terraform/getting-started/install.html)を参考に自分のマシンにあったインストールを行ってください。
### **Ansibleのインストール**
Ansibleは色々なインストール方法があるのですが、今回はvirtualenv上でpip経由でインストールしました。virtuaenvをインスとーしてない方は以下の方法でダウンロードし、virtualenvを有効にしてください。

    $ pip3 install virtualenv
    $ mkdir sample
    $ cd sample
    $ virtualenv --no-site-packages ansible
    $ source ansible/bin/activate # virtualenvが有効になる
    $ (ansible) ←　有効化された
    $ pip3 install ansible # インストール
    $ ansible --version # version確認

### AWS側でIAMユーザーを作成する
Terraformの実行にはIAMユーザーとそのシークレットキーとアクセスキーが必要になります。ここでは`terraform-user`というIAMユーザーを作成しました。また、シークレットキーとアクセスキーも控えておきましょう。**(シークレットキーとアクセスキーの取り扱いには気をつけてください。)**

## Terraformを動かしてみる  
まず、Terraformから動かしてみようと思います。プロジェクトディレクトリに`terraform.tfvars`というファイルを作成し、IAMユーザーのシークレットキーとアクセスキーを記述します。(**Gitなどのバージョン管理システムのの管理対象からは必ず外してください。**)

```:terraform.tfvars
access_key = "xxxxxxxxxxxx"
secret_key = "xxxxxxxxxxxx"
```
次に`variables.tf`というファイルを作成し、`terraform.tfvars`で定義した情報を変数として定義し、Terraformの実行ファイルから変数を参照できるようにします。

```:variables.tf
variable "access_key" {}
variable "secret_key" {}
```

それではいよいよTerraformの実行ファイルを作成し、動かしていきたいと思います。`main.tf`を作成し、次のように記述します。`region`と`ami`は任意の値に置き換えてください。

```:main.tf
provider "aws" {
  profile = "default"
  region = "ap-northeast-1"
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
}

resource "aws_instance" "example" {
  ami = "ami-xxxx"
  instance_type = "t2.micro"
}
```

最後に実行していきます。`-auto-approve`オプションを入力することで実行前の確認をスキップできます。コマンド実行後AWSのコンソールを確認すると、新しいEC2インスタンスが作成されているはずです。

    $ terraform init  # 初期化
    $ terraform apply -auto-approve# 実行

EC2が作成されたのが確認できたらリソースを削除していきます。削除コマンドは以下の通りです。


    $ terraform destroy -auto-approve
    Destroy complete! Resources: 1 destroyed.


## Terraformで実践的なインフラリソースを構築してみる
前のセクションではTerraformを使ってEC2インスタンスを起動しましたが、VPCやセキュリティグループはデフォルトのものを利用しており、キーペアも作成されていないのでSSHログインすることができません。このセクションではより実践的なインフラを構築していきます。

### EC2にSSHするための鍵を生成する
EC2はAWS側で作成したキーペアでSSH接続することが多いのですが、ここではキーペアをローカルPCで作成し、その公開鍵を元にEC2を作成していきます。

    $ ssh-keygen # 公開認証方式に必要なキーペアを作成
    Enter file in which to save the key (/Users/user-name/.ssh/id_rsa): # Enterで省略
    Enter passphrase (empty for no passphrase): # Enterで省略
    Enter same passphrase again: # Enterで省略

ここで作られた公開鍵を元にAWS側でキーペアを作成していきます。公開鍵の内容が必要になるのでcatコマンドでファイルの中身を表示し、控えておきます。  
    
    $ cat ~/.ssh/id_rsa.pub
    ssh-rsa ・・・・・・・・省略

次にAWSのコンソール画面でキーペアを作成します。キーペアのインポートから先ほどcatコマンドで出力した公開鍵の中身をコピペします。

![create-key.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286861/b210874a-c651-2ea6-4562-fb878fbe97f7.jpeg)


これで準備が完了しました。次にTerraformの実行ファイルを作成していきます。この例ではインスタンスサイズに`t2.medium`を使用しています。`t2.micro`インスタンスだとGitLabのインストール要件を満たさずインストールに失敗するからです。

```:main.tf
provider "aws" {
  profile = "default"
  region = "ap-northeast-1"
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
}

# VPC
resource "aws_vpc" "tf-vpc" {
  cidr_block = "10.0.0.0/16"
  instance_tenancy = "default"
  enable_dns_support = "true"
  enable_dns_hostnames = "true"
  tags = {
    Name = "tf-vpc"
  }
}

# internet gateway
resource "aws_internet_gateway" "tf-gw" {
  vpc_id = "${aws_vpc.tf-vpc.id}"
  
}

# piblic subnet
resource "aws_subnet" "tf-public-subnet" {
  vpc_id = "${aws_vpc.tf-vpc.id}"
  cidr_block = "10.0.10.0/24"
  availability_zone = "ap-northeast-1a"
}

# route table
resource "aws_route_table" "tf-pub-rt" {
  vpc_id = "${aws_vpc.tf-vpc.id}"

  route = {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.tf-gw.id}"
  }
}

#  An association between a route table and a subnet.
resource "aws_route_table_association" "tf-public_subnet" {
  subnet_id = "${aws_subnet.tf-public-subnet.id}"
  route_table_id = "${aws_route_table.tf-pub-rt.id}"
}

# EC2 instance
resource "aws_instance" "tf-ec2" {
  ami = "ami-068a6cefc24c301d2" # Amazon linux2
  instance_type = "t2.medium"
  key_name = "${aws_key_pair.tf-sample-key.id}"
  vpc_security_group_ids = ["${aws_security_group.tf-sg.id}"]
  subnet_id = "${aws_subnet.tf-public-subnet.id}"
  associate_public_ip_address = "true"
  tags = {
    Name = "tf-ec2"
  }
}

# key-pair
resource "aws_key_pair" "tf-sample-key" {
  key_name = "${var.key_name}"
  public_key = "${file(var.public_key_path)}"
}

# security group
resource "aws_security_group" "tf-sg" {
  name = "tf-sg"
  description = "tf test"
  vpc_id = "${aws_vpc.tf-vpc.id}"
    ingress {
        from_port = 22
        to_port = 22
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

```

`terraform.tfvars`と`variables.tf`にキーペアの名前とローカルにおいてある公開鍵のパス情報を追記します。

```:terraform.tfvars
~省略~
key_name = "tf-sample-key"
public_key_path = "~/.ssh/id_rsa.pub"
```
```variables.tf
~省略~
variable "key_name" {}
variable "public_key_path" {}
```
それではいよいよ動かしていきます。完了まで数分かかります。  

    $ terraform apply

リソースが作成されたら、試しにEC2インスタンスにssh接続してみてください。
    
    $ ssh -i .ssh/id_rsa ec2-user@<your-ec2-ip>

    [ec2-user@ip-10-0-10-157 ~]$

ここで一点、修正を加えたいと思います。現在作成されているセキュリティグループでは22番ポートのみ解放していますが、あとでブラウザから表示する必要があるので80番ポートを解放したいと思います。また、既存のルールだとアウトバウンドのルールが記載されておらず、Ansibleが外部リポジトリを参照できないので`main.tf`の一部を変更します。`aws_security_group`のブロックに直接ルールを記載せず、`aws_security_group_rule`に外出しして`security_group_id = "${aws_security_group.tf-sg.id}"`で紐付けを行っています。また、22番ポートを解放するインバウンドルールは明示的に記述していません。デフォルトで作成されるので明示的に22番ポートを解放するルールを作成してしまうと、エラーになります。なので、ここでは80番ポートの解放ルールのみ記述します。

```:main.tfの一部
~省略~

resource "aws_security_group" "tf-sg" {
  name = "tf-sg"
  description = "tf test"
  vpc_id = "${aws_vpc.tf-vpc.id}"
}

resource "aws_security_group_rule" "http-port" {
  type = "ingress"
  from_port = 80
  to_port = 80
  protocol = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = "${aws_security_group.tf-sg.id}"
}

resource "aws_security_group_rule" "out-bound" {
  type = "egress"
  from_port = 0
  to_port = 0
  protocol = "-1"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = "${aws_security_group.tf-sg.id}"
}

```
    
再度コマンドを実行します。

     $ terraform apply

マネジメントコンソール画面からセキュリティグループのインバウンドルールが変更されていることを確認します。

<img width="1085" alt="sg.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286861/23b5e7c5-6422-090b-8e81-9b401577014f.jpeg">


## Ansibleを動かしてみる
Terraformを使ってリソースを作成することができたので次はAnsibleでEC2にGitLabを構築していきます。
まずはカレントディレクトリにansible関連のファイルを保存する`ansible-project`ディレクトリを作成します。

    $ mkdir ansible-project
    $ cd ansible-project

Ansibleはコントロールノードからターゲットノードに対してplaybookを実行する際、SSHを利用します。なので、まずはSSHの接続情報が書かれた`ssh_config`ファイルを作成し、以下を記述します。`HostName`は任意の値に書き換えてください。

```:ssh_config
Host *
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
 
Host gitlab
  HostName ec2-<your ec2 instance>.ap-northeast-1.compute.amazonaws.com
  User ec2-user
  IdentityFile ~/.ssh/id_rsa
```

以下のコマンドでファイルを利用してssh接続できます。

    $ ssh -F ssh_config ec2-user@ec2-<your-ec2-ip>.ap-northeast-1.compute.amazonaws.com 

まずはいきなりGitLabを構築せず、Apacheをインストールし、起動するところから始めたいと思います。次のplaybookを用意します。

```:test.yml
- hosts: gitlab
  become: yes
  become_user: root
  tasks:
    - name: Install http server
      yum: 
        name: httpd

    - name: Start http server
      service: 
        name: httpd
        state: started
        enabled: yes
```

同ディレクトリに`hosts`ファイルを用意し、ターゲット情報を記載します。  

```:hosts
[gitlab]
gitlab
```

次のコマンドでplaybookを実行していきます。 

    $ ansible-playbook -i hosts test.yml 

すると。。。失敗しました。  

    fatal: [gitlab]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname gitlab: nodename nor servname provided, or not known", "unreachable": true}

Ansibleがターゲットノードに接続しようとしていますが、sshコマンドのオプションが明示されておらず失敗しています。`playbook.yml`と同じディレクトリに`ansible.cfg`を作成し、以下の設定を記載します。  

```ansible.cfg

[defaults]
inventory = hosts
retry_files_enabled = False
 
[privilege_escalation]
become = True
 
[ssh_connection]
control_path = %(directory)s/%%h-%%r
ssh_args = -o ControlPersist=15m -F ssh_config -q
scp_if_ssh = True
```

気を取り直してもう一度実行してみます。　

    $ ansible-playbook -i hosts test.yml 
    ~省略~
    ok: [gitlab]
     ____________________________ 
    < TASK [Install http server] >
     ----------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
    ~省略
    gitlab                     : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

確認のためにブラウザにEC2インスタンスのIPアドレスを入力します。以下のように表示されていれば成功です。  
<img width="1366" alt="hello-ansible.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286861/88c2e5b5-ba48-8914-04f3-ae1af158a029.jpeg">



## AnsibleでGitLabを構築する
それではいよいよ、AnsibleでGitLabを構築していきたいと思います。`playbook.yml`を作成し、以下を記述します。GitLabのインストールの詳細が知りたい方は[こちら](https://about.gitlab.com/install/)をご覧ください。  

```:playbook.yml
- name: Install GitLab
  hosts: gitlab
  tasks:
  - name: Upgrade all packages
    yum:
      name: '*'
      state: latest

  - name: Install a list of packages
    yum:
      name:
        - curl
        - golang
        - policycoreutils
        - openssh-server
        - openssh-clients
        - postfix
        - vim
        - wget
        - "@Development tools"
      state: latest

  - name: systemctl enabled sshd & systemctl start sshd
    systemd:
      name: sshd
      state: started
      enabled: yes

  - name: systemctl enabled postfix & systemctl start postfix
    systemd:
      name: postfix
      state: started
      enabled: yes


  - name: Download gitlab-ce
    get_url:
      url: https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh
      dest: /usr/local/src
      mode: '0755'
    become: yes
    become_user: root

  - name: Run script.rpm.sh
    shell: ./script.rpm.sh
    args:
      chdir: /usr/local/src
    become: yes
    become_user: root

  - name: Install gitlab-ce
    yum:
      name: gitlab-ce
      state: latest

  - name: gitlab-ctl reconfigure
    shell: gitlab-ctl reconfigure
    become: yes
    become_user: root
```

実行してみます。実行には数分かかります。また、サーバー上`sudo systemctl status httpd`を実行しApacheのプロセス停止させておいてください。新しい、playbookを実行したらApcheのプロセスが停止するのかと思ったのですが、実行状態のままでGitLabにアクセスすることができませんでした。(泣))

    $ ansible-playbook -i hosts playbook.yml 


ブラウザからアクセスし、GitLabのトップページが表示されていれば成功です。

<img width="1367" alt="hello-gitlab.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286861/adc5e05e-5f04-b765-fbe2-73c2f235c7e6.jpeg">


## リソースのクリーンアップ  
EC2のt2.mediumインスタンスは無料枠の対象外なので削除しておきます。また、このハンズオンで利用したTerraform用のIAMユーザーも念の為消しておきましょう。

    $ terraform destroy


## 参考資料
- [Ansibleチュートリアル 2017](https://dev.classmethod.jp/server-side/ansible/ansible-tutorial-2017/)
- [Ansibleを使用して、CentOS 7にGitLabをインストールする](https://qiita.com/GENO/items/6bf1a9d7e1cf41ed358f)
- [Terraform公式ドキュメント](https://www.terraform.io/docs/index.html)
- [Ansibleによるシステム構成管理：基礎からCloud Modulesを使ったAWS構築まで](https://www.udemy.com/course/ansible-aws-modules/)
- [Ansible実践ガイド 第3版](https://www.amazon.co.jp/Ansible%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E7%AC%AC3%E7%89%88-impress-top-gear/dp/4295007641/ref=asc_df_4295007641/?tag=jpgo-22&linkCode=df0&hvadid=342518373760&hvpos=1o1&hvnetw=g&hvrand=1040752812515999043&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009540&hvtargid=pla-831391632425&psc=1&th=1&psc=1)
- [GiLab公式ドキュメント](https://about.gitlab.com/install/)