SpinnakerをAWS上にデプロイする

# 概要

[AWSのこの記事](https://aws.amazon.com/jp/blogs/opensource/continuous-delivery-spinnaker-amazon-eks/) に従って、AWS上にSpinnakerをデプロイする手順を記述していきます。  

＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
(2020-01-06追記)
Spinnakerをとりあえず試す目的なのであれば、以下のページに従い、GCPにて実施するのが一番簡単でした。。
AWS・Azureにおけるこの手のクイックスタートは、手順通りやってもうまく行かなかったのですが、GCPはすんなりでした。
[Spinnaker Google Cloud Platform をインストールして使用する
](https://cloud.google.com/docs/ci-cd/spinnaker/spinnaker-for-gcp?hl=ja#install_and_use_spinnaker_google_cloud_platform)
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝

以下のように構築することを目指します。

![spinnaker-arch (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/21bb9523-c1ad-1b49-59f6-8e265dbd5f7a.png)

本記事では、Spinnakerの機能を利用したデプロイパイプラインの作成は扱いません。

以下の順番で記述していきます。

* AWS Cloud9 の環境を作成する
* AWS Cloud9 の環境を設定する
* Amazon EKS のクラスターを作成する
* Spinnaker をインストール、設定する
* クリーンアップ

## AWS Cloud9 の環境を作成する

ここでは[AWS Cloud9](https://aws.amazon.com/jp/cloud9/)から、クラスターの作成・Spinnakerの設定等を行います。  
そのため、ネットがつながりさえすれば同様の手順で構築が行えるはずです。

まずは、ブラウザからCloud9に接続できるようにします。

### Cloud9のVPC作成

Cloud9が所属するVPCの設定を、[ここのページ]( https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/vpc-settings.html#vpc-settings-create-vpc )にしたがって行います。

* [ VPCコンソール ](https://console.aws.amazon.com/vpc)にある、「VPCウィザードの起動」をクリック
* 「1個のパブリックサブネットを持つVPC」を選択
* 「VPC名」「サブネット名」に任意の名前を入力して、その他はデフォルトのまま「VPCの作成」をクリック
* VPCダッシュボードで、先程名付けた「VPC名」に該当するVPCを選択し、「説明」タブの「ネットワークACL」の右のリンクをクリックする
* 「インバウンドのルール」タブを選択し、「インバウンドのルールの編集ボタン」をクリックする。以下のように設定する
![範囲を選択_010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/d27070a4-ae60-5ccf-1394-db394065fbbb.png)

※ルールの100と200の送信元IPは、[ここ](https://ip-ranges.amazonaws.com/ip-ranges.json)からip-ranges.jsonを落として、以下のコマンドを実行したときに出力されたIPを入力します

```shell
jq '.prefixes[] | select(.service=="CLOUD9") | select(.region=="ap-northeast-1")' < ip-ranges.json
```

VPCができたら、コンソール画面でCloud9の画面までいき、Cloud9が動作するEC2を作成します。 
Cloud9作成画面では、以下のように各項目を選択します。
**Network settings(advanced) のところで先ほど作成したVPCを入力します。**
![範囲を選択_007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/a1bf1f24-24f8-1f66-2e03-e706af7cfa5b.png)

環境を作成するとCloud9の画面に飛べるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/adca8896-b9d7-4d46-f3b8-11f1e8a73cbe.png)

## AWS Cloud9 の環境を設定する

次に、Spinnakerをデプロイしたり、k8sへの接続確認をするために必要なツールを、Cloud9にインストールしていきます。

### kubectl, aws-iam-authenticator をCloud9にインストールする

```shell
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl

chmod +x ./kubectl

sudo mv ./kubectl /usr/local/bin/kubectl

curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.13.7/2019-06-11/bin/linux/amd64/aws-iam-authenticator

chmod +x ./aws-iam-authenticator

mkdir -p $HOME/bin && cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator && export PATH=$HOME/bin:$PATH

echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc

aws-iam-authenticator  help
```

### awscliをアップグレードする

```shell
pip install awscli --upgrade --user
```

### eksctlをインストールする

```shell
curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp

sudo mv /tmp/eksctl /usr/local/bin
```

### Terraformをインストールする

```shell
wget https://releases.hashicorp.com/terraform/0.12.4/terraform_0.12.4_linux_amd64.zip
unzip terraform_0.12.4_linux_amd64.zip
sudo mv terraform /usr/local/bin/
export PATH=$PATH:/usr/local/bin/terraform
```

### Halyardをインストールする

HalyardはSpinnakerをデプロイするに当たって使用するツールです。  
[公式ドキュメント](https://www.spinnaker.io/setup/install/halyard/)によると、Halyardを使用せずともSpinnakerの構築はできるようですが、Halyardを使用したSpinnakerデプロイが推奨されています。

```shell
curl -O https://raw.githubusercontent.com/spinnaker/halyard/master/install/debian/InstallHalyard.sh
sudo bash InstallHalyard.sh
sudo update-halyard
hal -v
```

## Amazon EKS のクラスターを作成する

Amazon EKSのクラスターを3つ作成します。

### 強い権限のユーザーを登録する

AdministratorAccess権限を持ったIAMユーザーを作り、`aws configure --profile spin` でAWSに接続できるように登録します。(コマンド実行時にアクセスキー、シークレットキー等聞かれるので、適宜入力してください)
このユーザーにてAmazon EKSクラスターを作成していきます。
**が、[最小権限の原則](https://ja.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E6%A8%A9%E9%99%90%E3%81%AE%E5%8E%9F%E5%89%87)を鑑みると、普通に駄目だと思います。ちゃんとした場面では考慮が必要です。**

### eks-prod クラスターを作成する

```shell
export AWS_PROFILE=spin
eksctl create cluster --name=eks-prod --nodes=3
```

### eks-uat クラスターを作成する

```shell
export AWS_PROFILE=spin
eksctl create cluster --name=eks-uat --nodes=3
```

### eks-spinnaker クラスターを作成する

```shell
export AWS_PROFILE=spin
eksctl create cluster --name=eks-spinnaker --nodes=2
```

※`eksctl create cluster`コマンドは20分位かかったりするので、Cloud9で複数タブ開いて同時に実行するのがおすすめです

## Spinnaker をインストール、設定する

eks-spinnakerクラスターにSpinnakerをデプロイします。

### kubectlにおけるcontextを追加する

kubectlコマンドでEKSクラスターに接続できるように設定します。

```shell

aws eks update-kubeconfig --name eks-spinnaker --region ap-northeast-1 --alias eks-spinnaker

aws eks update-kubeconfig --name eks-uat --region ap-northeast-1 --alias eks-uat

aws eks update-kubeconfig --name eks-prod --region ap-northeast-1 --alias eks-prod
```

### Docker registoryを作成する

Docker Hubを使用することをHalyardに教え込みます。
nannany-docker、nannany の部分は適宜変えてください。

```shell

hal config provider docker-registry enable 

hal config provider docker-registry account add nannany-docker --address index.docker.io --username nannany --password
```

### Kubernetesアカウントを設定する

#### eks-prodクラスターの設定

eks-prodクラスタに対して、サービスアカウントを作成します。

```shell

hal config provider kubernetes enable

kubectl config use-context eks-prod

CONTEXT=$(kubectl config current-context)

kubectl apply --context $CONTEXT -f https://spinnaker.io/downloads/kubernetes/service-account.yml
```

作成したサービスアカウントからトークンを抜き出します。

```shell

TOKEN=$(kubectl get secret --context $CONTEXT \
   $(kubectl get serviceaccount spinnaker-service-account \
       --context $CONTEXT \
       -n spinnaker \
       -o jsonpath='{.secrets[0].name}') \
   -n spinnaker \
   -o jsonpath='{.data.token}' | base64 --decode)
```

kubectlの設定をします。

```shell

kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN

kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user
```

eks-prodクラスターに対しては、k8sをProviderとすることをHalyardに教え込みます。

```shell

hal config provider kubernetes account add eks-prod --provider-version v2 \
 --docker-registries nannany-docker --context $CONTEXT
```

#### eks-uatクラスターの設定

eks-uatクラスタに対して、サービスアカウントを作成します。

```shell 
kubectl config use-context eks-uat 

CONTEXT=$(kubectl config current-context) 

kubectl apply --context $CONTEXT \
    -f https://spinnaker.io/downloads/kubernetes/service-account.yml
```

作成したサービスアカウントからトークンを抜き出します。

```shell

TOKEN=$(kubectl get secret --context $CONTEXT \
   $(kubectl get serviceaccount spinnaker-service-account \
       --context $CONTEXT \
       -n spinnaker \
       -o jsonpath='{.secrets[0].name}') \
   -n spinnaker \
   -o jsonpath='{.data.token}' | base64 --decode)
```

kubectlの設定をします。

```shell

kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN

kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user
```

eks-uatクラスターに対しては、k8sをProviderとすることをHalyardに教え込みます。

```shell 

hal config provider kubernetes account add eks-uat --provider-version v2 \
 --docker-registries nannany-docker --context $CONTEXT
```

#### eks-spinnakerクラスターの設定

eks-spinnakerクラスタに対して、サービスアカウントを作成します。

```shell

kubectl config use-context eks-spinnaker 

CONTEXT=$(kubectl config current-context)

kubectl apply --context $CONTEXT \
    -f https://spinnaker.io/downloads/kubernetes/service-account.yml
```

作成したサービスアカウントからトークンを抜き出す

```shell

TOKEN=$(kubectl get secret --context $CONTEXT \
   $(kubectl get serviceaccount spinnaker-service-account \
       --context $CONTEXT \
       -n spinnaker \
       -o jsonpath='{.secrets[0].name}') \
   -n spinnaker \
   -o jsonpath='{.data.token}' | base64 --decode)
```

kubectlの設定をします。

```shell

kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN

kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user
```

eks-spinnakerクラスターに対しては、k8sをProviderとすることをHalyardに教え込みます。

```shell

hal config provider kubernetes account add eks-spinnaker --provider-version v2 \
 --docker-registries nannany-docker  --context $CONTEXT
```

### Spinnakerを分散インストールすることをHalyardに教え込む

```shell
hal config deploy edit --type distributed --account-name eks-spinnaker
```

### SpinnakerでS3を使えるように設定する

<access-key>には、クラスターを作成するときに使用したIAMユーザーのアクセスキーを入力します。
コマンド実行後、secret access keyを求められるので、入力してください。

```shell

export YOUR_ACCESS_KEY_ID=<access-key>

hal config storage s3 edit --access-key-id $YOUR_ACCESS_KEY_ID  --secret-access-key 
```

Halyardにストレージとして、S3を使用することを教え込みます。

```shell

hal config storage edit --type s3
```

### インストールするSpinnakerのバージョンを指定し、デプロイ

どのようなSpinnakerのバージョンがあるか確認します。

```shell

hal version list
```

Spinnakerのバージョンを確認したら、Spinnakerのデプロイを実施します。

```shell

export VERSION=1.17.5
hal config version edit --version $VERSION

hal deploy apply
```

### Spinnaker がデプロイされているか確認する

```shell

kubectl -n spinnaker get svc
```

うまく行っていたら、以下のようになります。

```
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
spin-clouddriver   ClusterIP   10.100.74.194    <none>        7002/TCP   98s
spin-deck          ClusterIP   10.100.253.86    <none>        9000/TCP   97s
spin-echo          ClusterIP   10.100.216.222   <none>        8089/TCP   100s
spin-front50       ClusterIP   10.100.212.252   <none>        8080/TCP   98s
spin-gate          ClusterIP   10.100.29.33     <none>        8084/TCP   98s
spin-igor          ClusterIP   10.100.22.213    <none>        8088/TCP   99s
spin-orca          ClusterIP   10.100.220.102   <none>        8083/TCP   99s
spin-redis         ClusterIP   10.100.41.242    <none>        6379/TCP   99s
spin-rosco         ClusterIP   10.100.201.6     <none>        8087/TCP   100s
```

### Elastic Loadbalancerを使用して、Spinnakerを公開する

```shell

export NAMESPACE=spinnaker

kubectl -n ${NAMESPACE} expose service spin-gate --type LoadBalancer \
  --port 80 --target-port 8084 --name spin-gate-public 

kubectl -n ${NAMESPACE} expose service spin-deck --type LoadBalancer \
  --port 80 --target-port 9000 --name spin-deck-public  

export API_URL=$(kubectl -n $NAMESPACE get svc spin-gate-public -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') 

export UI_URL=$(kubectl -n $NAMESPACE get svc spin-deck-public -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') 

hal config security api edit --override-base-url http://${API_URL} 

hal config security ui edit --override-base-url http://${UI_URL}

hal deploy apply
```

### Spinnakerコンソールにログインする

再度`kubectl -n spinnaker get svc`コマンドを実行すると、以下のような出力になります。


```
NAME               TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)        AGE
spin-clouddriver   ClusterIP      10.100.81.71     <none>                                                                         7002/TCP       3m47s
spin-deck          ClusterIP      10.100.227.41    <none>                                                                         9000/TCP       3m46s
spin-deck-public   LoadBalancer   10.100.82.52     ~~~~~~~~~~~.ap-northeast-1.elb.amazonaws.com   80:30884/TCP   2m36s
spin-echo          ClusterIP      10.100.58.79     <none>                                                                         8089/TCP       3m46s
spin-front50       ClusterIP      10.100.65.76     <none>                                                                         8080/TCP       3m45s
spin-gate          ClusterIP      10.100.255.44    <none>                                                                         8084/TCP       3m45s
spin-gate-public   LoadBalancer   10.100.225.51    ~~~~~~~~~~~.ap-northeast-1.elb.amazonaws.com    80:30648/TCP   2m36s
spin-igor          ClusterIP      10.100.37.116    <none>                                                                         8088/TCP       3m46s
spin-orca          ClusterIP      10.100.204.159   <none>                                                                         8083/TCP       3m46s
spin-redis         ClusterIP      10.100.12.240    <none>                                                                         6379/TCP       3m47s
spin-rosco         ClusterIP      10.100.188.226   <none>                                                                         8087/TCP       3m44s
```

`spin-deck-public`のEXTERNAL-IPにアクセスするとSpinnakerのUIにアクセスできます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/38841cf5-98d0-4aa3-ad31-c9024eee5b34.png)

# その他

備忘録として、躓いた点・解決してない点を残しておきます。

* Spinnakerをデプロイするクラスターを作成するときに、節約しようとノードのインスタンスタイプを`t2.micro`にすると、podが起動しません。describeで確認すると、`Insufficient pods.`というメッセージが出ていました。下記の記事によると、どうやら各インスタンスタイプ毎に割り当てられるIPの数が決まっていて、`t2.micro`では足りなかったようです。`eksctl create cluster`はデフォルトだとインスタンスタイプ`m5.large`で作成するのですが、その場合はうまくいきます。
    * https://qiita.com/aoshimash/items/53723f8f99b26e06b9e7
    * https://qiita.com/os1ma/items/ba6cbcaeb72b5383373c
* [「強い権限のユーザーを登録する」](#強い権限のユーザーを登録する) の部分を行わず、EKSクラスターの作成を行った場合、`kubectl get all`等の`kubectl`コマンドで接続に行った際に、下記のようなエラーが出力され、接続ができませんでした。

```
error: You must be logged in to the server (Unauthorized)
```