JenkinsからCloudformation、Ansibleのジョブ実行の流れ

##Jenkinsでジョブ実行の流れ
＊簡単な流れの説明なので、あらゆるファイルの作成、インストール方法などは省略してます。
＊事前に作成したアプリ用のcloudformationスタック、Ansibleが用意してある前提です。

**大まかな流れとして**
Jenkinsサーバー作成（EC2A）
↓
Jenkins起動
↓
各種ジョブ実行（EC2Bへの環境作成）
・cloudformationスタックのリポジトリをクローンするジョブ
・クローンしてきたcloudformationスタックを実行するジョブ
・Ansibleのリポジトリをクローンするジョブ
・クローンしてきたAnsibleを実行するジョブ

~~＊余裕があれば別記事で（作成次第リンク貼ります）~~
**追記： JenkinsとGithubの連携の記事を作成しました。**

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

Webhookを使用し、JenkinsとGithubの連携。
Githubに何かPushされた時に自動でJenkinsが起動するようにする。



・登場人物
Jenkinsおじさん

##構成図

![jenkins構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650745/5989bd95-9c7b-829d-b6f0-ff7a0e8eafd4.png)


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


##EC2にjenkinsサーバーを立ち上げる

方法はいくつかある
１、手動で作る
２、CloudFormationを使用する
３、Ansibleを使用する

どのやり方でもOK（ここではやり方省略）
**追記：CloudFormationで作成した記事を作りました。**

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

＊SGポート22(ssh用)と８０８０（jenkins用）を開放しておく
＊IamUserでjenkinsユーザー作成する
＊t2small以上出ないとサーバーダウンする。
＊ElasticIP定義したほうが楽

＊Jenkins(java), Ansible, gitをインストール
↓コード掲載


```
$ 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
```


＊Jenkinsサーバーをcloudformationで立ち上げる場合
（Cloudformationのユーザーデータのコードがうまく機能してなかったら、下記のコードでEC2立ち上げた時のログが見れる。）
（sudo cat /var/log/cloud-init-output.log）

・http://パブリックIPアドレス:8080でJenkinsにログインし設定する
（やり方は省略）


#各種ジョブ実行
##●cloudformationスタックのリポジトリをクローンするジョブ
-事前に構築しておいたcloudformationスタック（アプリ用）をgithubからcloneしてくるジョブ設定

t2microでOK。ElasticIPも定義する。RDS必要に応じて、SGも。EC2セキュリティグループ（rails用３０００番追加）

・まずは**新規ジョブ作成**
・ジョブ名入力⇨フリースタイル・プロジェクトのビルドを選択し、OK
・ソースコード管理でGithubからCloudformationのyamlファイル（フォルダごとでも良い）を引っ張ってくる
・ビルドするブランチ欄を*/main（もしくはmaster)にする（デフォルトブランチに合わせて）
・Apply押して保存
・ビルド実行
・そうすると/var/lib/jenkins/workspaceの中にジョブ作成時のジョブ名のフォルダがあり、その中にGithubからクローンしてきたファイルがある



##●クローンしてきたcloudformationスタックのジョブ実行
JenkinsおじさんにAWS CLI経由でスタックを作ってもらう必要がある

####まずはJenkinsユーザーがCLIを使えるようにEC2に情報を登録する。

1,Jenkinsユーザーが追加されてるか確認
（Linux ユーザー　確認で検索）
sudo cat /etc/passwd

2,AWSコンソールのIAMからcfnで立ち上げたIamユーザーの名前をクリック。
3,認証情報からアクセスキーの作成、コピー
4,EC2Aにログインし、コマンド入力
（sudo -u jenkins aws configure）
5,先ほどコピーしたアクセスキー、シークレットキーを入力。
6,東京リージョン（ap-northeast-1）、アウトプットは空で良い（もしくはjson）
ここまででjenkinsユーザーでaws configure作成完了


####シェルの実行欄にCLIコマンドを入力してcloudformationスタックの起動設定をする

＊２種類のコマンドが必要

１、スタックを実行するコマンド
（aws cloudformation create-stack もしくは　aws cloudformation deploy）
＊付属コマンドの書き方もそれぞれ異なる

２、jenkinsおじさんに、ビルド実行後スタックが完全に作り終わるまで待っててもらうコマンド（ポーリング処理）
（aws cloudformation wait stack-create-complete）

#####さっきgit cloneしたcloudformationジョブの設定を再び開き、シェルの実行欄に記述
（ビルド⇨ビルド手順の追加からシェルの実行欄を呼び出す）

```
・書き方
aws cloudformation create-stack \
  --stack-name （スタックの名前） \
  --template-body file://$WORKSPACE/（アプリをデプロイするEC2環境を作る）cloudformationのyamlファイル

aws cloudformation wait stack-create-complete \
  --stack-name （スタックの名前）
```


```
例1）
aws cloudformation create-stack \
  --stack-name foo \
  --template-body file://$WORKSPACE/new.yaml

aws cloudformation wait stack-create-complete \
  --stack-name foo

例２）
aws cloudformation deploy \
  --stack-name jenkins \
  --template-file new.yml \
  --parameter-overrides $(cat new_dev.cfg) \
  --capabilities CAPABILITY_NAMED_IAM

aws cloudformation wait stack-create-complete \
  --stack-name jenkins
```


＊　$WORKSPACE=/var/lib/jenkins/ビルドの名前/

＊上記でも説明したように、コマンドによって付属コマンドの書き方も違うので注意。
　それぞれのコマンドの意味については調べてください。必須なのはスタック名とテンプレート名。

＊S3バケットにスタックの名前反映させる時は、大文字使えない、一意じゃなければいけないなど名前に注意する必要ある

#####入力できたら保存してビルドの実行


##●Ansibleのリポジトリをクローンするジョブ

*Cloudformationの時と流れは同じ
・まずは**新規ジョブ作成**
・ジョブ名入力⇨フリースタイル・プロジェクトのビルドを選択し、OK
・ソースコード管理でGithubからAnsibleファイル（フォルダごとでも良い）を引っ張ってくる
・ビルドするブランチ欄を*/main（もしくはmaster)にする（デフォルトブランチに合わせて）
・Apply押して保存
・ビルド実行
・そうすると/var/lib/jenkins/workspaceの中にジョブ作成時のジョブ名のフォルダがあり、その中にGithubからクローンしてきたファイルがある

・/var/lib/jenkins/workspace に保存されてるか確認
・jenkinsユーザーでAnsible使えるか確認
シェルの実行欄に`$ ansible —version　`と入力し、ジョブ実行
⇨実行できればAnsibleがインストールされてることが確認できる

##●クローンしてきたAnsibleを実行するジョブ

####Ansibleで定義してる秘密鍵をローカルからJenkinsサーバーに移す

EC2A（Jenkinsサーバー）からEC2B（ターゲットノード）にsshログインするのには秘密鍵と公開鍵が必要
（前提としてEC2AとEC2Bに同一の鍵を作成している必要がある）

現在EC2Aに秘密鍵が無いため、EC2AからEC2Bにsshすることができない
そこでローカルから秘密鍵をEC2Aにコピーしてくる必要がある

**手順**
scpコマンドで/var/lib/jenkins/.sshへ送ろうとしてもpermission deniedになる
（jenkinsリポジトリ配下は権限がjenkinsユーザーになってるから）

・まずは/home/ec2-user/.sshへコピーする

```
書き方
$ scp -i <EC2Bへのssh接続で使用する秘密鍵> <ローカルの秘密鍵ファイルPATH> <ユーザー名>@<接続先IPv4アドレス>:<秘密鍵の配置先PATH>
```
```
例
$ scp -i ~/.ssh/udemysample1.pem ~/.ssh/udemysample1.pem ec2-user@54.95.40.69:/home/ec2-user/.ssh/
```

・ファイルが ls .sshで転送されてるか確認
・/home/ec2-user/.sshにある鍵をさらにjenkinsディレクトリへコピーする

```
$ sudo cp ~/.ssh/udemysample1.pem /var/lib/jenkins/.ssh/
```

Jenkinsディレクトリの中をls -laで確認　.ssh root権限になってるはず
**追記**：この時点で.sshはあるけど、なぜかコピーされてない。青文字になってないから？
とにかく.sshにcdで入ろうとするとNot a directoryと言われる。
その場合下記参照

＊/var/lib/jenkinsディレクトリ上で`sudo rm .ssh`で一旦削除
`sudo mkdir -p .ssh/`で作成
再び`sudo cp ~/.ssh/udemysample1.pem .ssh/`でコピーする。

・ls-laで確認すると青文字で.sshが存在してる。権限がrootになってるのでこれをjenkinsユーザーに変更
sudo chown -R jenkins:jenkins .ssh
これで秘密鍵をEC2A（jenkinsサーバー）配下にコピー完了。


####Ansibleジョブの設定からシェルの実行欄にコマンド記述

```
例
ansible-playbook -i inventory site.yml
```

＊書き方はAnsibleフォルダの作り方によって色々あるので皆さんが作成したフォルダの構成に合わせてコマンドを実行してください。

＊inventoryのパブリックIP変更し忘れ注意
Erastic IPならOK

また、--checkをつけることで仮実行（実際には構築されない。つまりテスト）ができる

```
仮実行例
ansible-playbook -i inventory site.yml --check
```

・保存して、ビルド実行

ここまででJenkinsからCloudformation、Ansibleのジョブ実行が完了。

**追記：**
ここからさらにWebhookを使用して、JenkinsとGithubを連携させる場合は下記の記事を参照。

**続きの記事:** 

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

Webhookを使用し、JenkinsとGithubの連携。
Githubに何かPushされた時に自動でJenkinsが起動するようにする。


##Jenkins　エラー集

**エラー１**
・git cloneジョブ実行時
`"Couldn't find any revision to build. Verify the repository and branch configuration for this job."`

Githubのデフォルトブランチが原因らしい
main masterなど
具体的にはなぜかわからないが解決した。

やったこと（参考にしないでほしい）
~~デフォルトブランチをmainからmasterに変えて、再びmasterからmainへ戻した
その後、デフォルトのmainブランチにて
remotes/origin/master、などのブランチをgit mergeした
（Everything up-to-dateという結果になった）
再びjenkinsジョブ実行したら成功した~~~

＊**解決策（たぶん）**
ビルドするブランチ欄を*/main（もしくはmaster）にする（githubのデフォルトブランチに応じて）
そしたらmain(master)リポジトリから引っ張ってくれるので、デフォルトブランチとリンクさせれば同期されると思う

**エラー２**
・`You must specify a region. You can also configure your region by running "aws configure"`

**解決策**⇨LINUXユーザー名（sudo cat /etc/passwdで表示されたユーザー名）と同じIAMユーザー名を作る。
今回の場合はjenkinsユーザー作成し、aws configureでCLIにキーの登録をする必要あり

**エラー３**
・`invalid template path file:///var/lib/jenkins/workspace~`

**原因**⇨cloudformationスタックをCLIコマンドで記述する際のfileとbodyの書き方の違いだった

間違った例）
—template-file file://$WORKSPACE/new.yml

正解例）
aws cloudformation deploy
  —template-file new.yml

もしくは

aws cloudformation create-stack
  —template-body file://$WORKSPACE/new.yml



**エラー４**
・scpコマンド時
`Permission denied (publickey,gssapi-keyex,gssapi-with-mic)`
**原因**⇨コマンドが間違ってただけ
scpと-iの順番
sudoはどっちでもいい

間違いコマンド
sudo -i scp ~/.ssh/udemysample1.pem ~/.ssh/udemysample1.pem ec2-user@54.95.40.69:/home/ec2-user/.ssh/

正解コマンド
scp -i ~/.ssh/udemysample1.pem ~/.ssh/udemysample1.pem ec2-user@54.95.40.69:/home/ec2-user/.ssh/

**エラー５**
JenkinsでAnsibleジョブ実行時
・`fatal: [54.95.40.69]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Failed to add the host to the list of known hosts (/var/lib/jenkins/.ssh/known_hosts).\r\nno such identity: /var/lib/jenkins/.ssh/udemysample1.pem: Not a directory\r\nPermission denied (publickey,gssapi-keyex,gssapi-with-mic).", "unreachable": true}`

**原因**⇨Jenkins配下に秘密鍵がちゃんとコピーされてなかった





**・他にもunreachableエラー**
Ansibleの設定ファイルの中身の設定

**確認すること**
１、まずターゲットノードのアドレス
（cloudformationで作成したEC2Bのアドレス）

２、鍵を保管してる場所
~/.ssh/udemysample1.pem

３、ssh接続先のユーザー情報
ec2-user


**他にもしょうもないエラーはいくつかありました。**
・s3stack nameに大文字を使ってしまった
ymlファイルでs3 stack nameにcfnのテンプレートファイル名を入れてた。
Cfnテンプレートファイル名に大文字を入れてたためエラー。



###作成終わって素朴な疑問
・Jenkins実行時、cloudformationで作成したスタックで決めた値をAnsibleにどうやって反映させるか
RDSのエンドポイントなど

下記の３点が変わらなければRDSインスタンスを作り直してもエンドポイントは変わらないらしい
* RDSのインスタンス識別子
* AWSアカウントID
* RDSインスタンスを配置するリージョン


⇨試しに今まで作ったRDS復元してみる
⇨確認取れた。