CircleCI から AWS VPC の private subnet に private repo を git clone

この記事は[CircleCI Advent Calendar 2015](http://qiita.com/advent-calendar/2015/circleci)の7日目の為に考えていた記事です。

あまりに時間が立ってしまったので、[元記事][original]から切り離して再投稿。

[original]: http://qiita.com/kotatsu360/items/149dd1b3a31e1550a2cb

# やりたいこと

1. **AWS VPC**上に組んだ**プライベートサブネット**上に
2. CircleCIから**Vagrant**でインスタンスを生成して
3. GitHubの**プライベートリポジトリ**をクローンする

図にするとこう。

![image](https://qiita-image-store.s3.amazonaws.com/0/62601/719f3def-c531-880a-0f5b-c365d258eeee.png)

# 動機
インターネットむき出しじゃないインスタンスに対して、SSHの鍵をばら撒かずにデプロイしたい

# 前提知識
## プライベートサブネット構成

以下の図のように、ユーザからのアクセスをELB経由に限定してアプリケーションサーバ自体は直接インターネットに接続できない**プライベートサブネット**に置く構成。

アプリケーションサーバを直接インターネットに晒したくない人向け。

ただ、全ての通信を遮断してしまうと、リリースもできない。時刻同期もできない。となってしまうので、そういった管理上必要な通信は別途用意するNATサーバ経由で行う。

![image](https://qiita-image-store.s3.amazonaws.com/0/62601/a35912a3-ae87-775f-4e94-ef8c72ceabf4.png)

プライベートサブネットに置かれたサーバからの通信は全てNAT経由になるので、
一枚目の画像についても正しくはこういうなる。
![image](https://qiita-image-store.s3.amazonaws.com/0/62601/eaecdcfb-bbff-d3a8-f6c2-3511489434a8.png)


[参考：classmethod - Amazon VPCでELBとNATを使ってよりセキュアな環境を作る【5日目】](http://dev.classmethod.jp/cloud/amazon-vpc-elb-nat/)

2015年の年末に[マネージドNATゲートウェイ](http://aws.typepad.com/aws_japan/2015/12/managednat.html)が新機能としてリリースされたので、今後NATサーバを別に立てる必要はないのかも。

# 本題

こちらのリポジトリにまとめました。内容そのままでプライベートリポジトリに変更してCircleCIでビルドすると動くと思います。
https://github.com/kotatsu360/private-repo-deploy-sample

サンプルでは、VagrantとChefを使っています。

## その1：プライベートサブネットのインスタンスへアクセスするための記述
まず、プライベートサブネットに踏み台（NATサーバ）経由でアクセスしてやる必要があります。
重要なのは2箇所です。

```bash:pre.sh
echo "Host nat" >> ~/.ssh/config
echo "  Hostname ${AWS_NAT_SERVER_IP_ADDRESS}" >> ~/.ssh/config
echo "  User ec2-user" >> ~/.ssh/config
```

```ruby:Vagrantfile
    override.ssh.username = "ubuntu"
    override.ssh.proxy_command = "ssh -A nat -W %h:%p"
```

ここでは、NATサーバがAmazonLinux, アプリケーションサーバがUbuntuの想定です。
まずpre.shの中でCircleCIの.ssh/configにNATの情報を書き込んでやります。

Vagrantでアクセスするときは、そのHostを使ってプロキシします。


また、この記事の範囲からは外れますが、pre.shはデプロイ前後で自分自身からのSSHを許可して、また閉じています。これは「普段はSSHを不許可、デプロイ時のみ許可」という運用をしたいためです。

```
※環境変数は最後に設定します
```


## その2：プライベートリポジトリにへアクセスするための記述

```ruby:Vagrantfile
  config.ssh.forward_agent = true
  # ...

    override.ssh.private_key_path = [ENV['GITHUB_CLONE_SSH_KEY_PATH'], ENV['AWS_LOGIN_SSH_KEY_PATH']]

```

```ruby:default.rb
file "/etc/sudoers.d/root_ssh_agent" do
  mode 0440
  owner 'root'
  group 'root'
  content "Defaults env_keep += \"SSH_AUTH_SOCK\"\n"
end

bash "add ssh_setting to .ssh/config" do
  not_if %!grep Host github.com /root/.ssh/config!

  code <<-EOC
    echo -e "Host github.com\n StrictHostKeyChecking no\n" >> /root/.ssh/config
  EOC
end
```

### override.ssh.private_key_path
VagrantでSSHのAgentForwardを有効にしています。
また、

* GitHubへアクセスするための鍵
* Chefがインスタンスへアクセスするための鍵

を指定しています。ここで指定した鍵がAgent Forwardで転送されます。

###   content "Defaults env_keep += \"SSH_AUTH_SOCK\"\n"
EC2インスタンスへSSHしているユーザは`ubuntu`。
Chefは`root`で動作する。ここでは`root`へ引き継ぐ環境変数にssh-agentを追加

### echo -e "Host github.com\n StrictHostKeyChecking no\n" >> /root/.ssh/config
GitHubからはsshで`git clone`する。`git clone`するときにfingerprintの確認で止まるのを防ぐために`StrictHostKeyChecking no`を追加

## その3：CircleCI
最後にCircleCIの「Environment variables」で環境変数を設定してやります。

```
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
についてはCircleCIの「AWS Permission」から設定してもいいかも。
```

| 変数名 | 概要 | サンプル |
| --- | --- | --- |
| AWS_ACCESS_KEY_ID | AWSのアクセスキー | xxxxCIBA	
| AWS_DEFAULT_REGION | AWS EC2のデフォルトリージョン| 	xxxxst-2	
| AWS_DEPLOY_MACHINE_SECURITY_GROUP | EC2インスタンスに割り当てるセキュリティグループ | 	xxxx228c	
| AWS_DEPLOY_SUBNET | EC2インスタンスを立てるサブネット | 	xxxxbc2c	
| AWS_KEYPAIR | AWSで作成するEC2インスタンスにSSHする時用keypair名 | 	xxxxpair	
| AWS_LOGIN_SSH_KEY_PATH | ※1 | 	
| AWS_NAT_SECURITY_GROUP | デプロイに使うNATのセキュリティグループ | 	xxxx11db	
| AWS_NAT_SERVER_IP_ADDRESS | デプロイに使うNATのEIP | 	xxxx9.71	
| AWS_SECRET_ACCESS_KEY | AWSのシークレットアクセスキー | 	xxxx1muU	
| GITHUB_CLONE_SSH_KEY_PATH | ※2 | 

### AWS_LOGIN_SSH_KEY_PATH
AWS_KEYPAIRで設定したkeypairの秘密鍵をCircleCIの「SSH Permission」からHostnameを空にして登録する。
![スクリーンショット 2016-01-30 15.56.27.png](https://qiita-image-store.s3.amazonaws.com/0/62601/ebc45a2c-69a7-84c4-3153-551c1ac55fbe.png)


![スクリーンショット 2016-01-30 15.56.54.png](https://qiita-image-store.s3.amazonaws.com/0/62601/377cce31-9917-9edc-3e20-6ba05cff2e5a.png)

Hostnameが空の場合、CircleCIはfingerprintに従って鍵を作成するので、そのパスを指定する。

```
~/.ssh/id_xxxxxxxxxxxx3044
```

### GITHUB_CLONE_SSH_KEY_PATH
CircleCIに一度SSHする。`~/.ssh/id_github.com`という鍵（CircleCIがリポジトリをCheckoutするための鍵, GitHubでいうDeployKey）があるのでこれの中身を「SSH Permission」にHostname=github.comで登録する。

![スクリーンショット 2016-01-30 16.14.15.png](https://qiita-image-store.s3.amazonaws.com/0/62601/0cdb25c4-a0e1-e104-c1a0-4a5620573536.png)

![スクリーンショット 2016-01-30 16.14.59.png](https://qiita-image-store.s3.amazonaws.com/0/62601/8e56e29c-d456-980b-b26f-6d41518715cd.png)

Hostnameが空でない場合、CircleCIはHostnameに従って鍵を作成するので、そのパスを指定する。

```
~/.ssh/id_github.com
```

# 制約
https://github.com/kotatsu360/private-repo-deploy-sample
このリポジトリのやり方だと、毎回Vagrantで新しいインスタンスを立ててしまうので
古いインスタンスは削除されるようにdeploy.shで設定しています。

**EC2インスタンスの中にデータを入れておくと漏れ無く消えます。**

# 振り返ってみて
「GitHubからクローンしたいリポジトリ」 = 「CircleCIでテストしているリポジトリ」の場合、シンプルにSCPで送るという手段もあったような。。

# 参考
[怠け者のためのVagrant+Chef入門 - Qiita](http://qiita.com/aKunihiroIshiguro/items/b3b4e87e9302e6e0aa33)
[CircleCIからサーバへのSSH接続確立時間を高速化する](http://stormcat.hatenablog.com/entry/2015/02/18/005045)