Amazon EKSでStackStormを動かす

# 構成

- Multi-AZ High Availability
- Kubernetes: Amazon EKS
- MongoDB: helm で `stable/mongodb-replicaset` の chart をインストールして使う
- RabbitMQ: helm で `stable/rabbitmq-ha` の chart をインストールして使う
- Redis: Amazon ElastiCache の Redis インスタンスを使う
- PostgreSQL: Amazon RDS の Aurora Postgres インスタンスを使う
- `/opt/stackstorm/packs`, `/opt/stackstorm/virtualenvs` に必要な RWX PV は kubernetes-incubator にある efs-provisioner を使う
- AWS region: us-west-2 (Oregon) に構築する
    - 前述した全てのAWSコンポーネントが揃っているため。

# 構築手順

## EKS

- `eksctl` をインストール
- `eksctl create cluster` でクラスタを作成
- `export KUBECONFIG=/path/to/eksctl/generated/kubeconfig` しておく
- ノード用に作成された Auto Scaling Group の Launch Configuration を編集して nfs-utils がインストールされたノードを作り直す
    - EC2 -> Launch Configurations のページで `eksctl` によって作成された Launch Configuration をコピー
    - Edit details -> Advanced Details -> User data のフィールドに、ノード初期化用の shell sccript が記載されているので、一番最後の行 (`/opt/aws/bin/cfn-signal`) の一つ前に `yum -y install nfs-utils` を追加し、保存
    - Auto Scaling Groups のページで `eksctl` によって作成された Node Group を編集、上で複製して作成した Launch Configuration に変更
    - いったんノード数の項目 (Desired Capacity / Min / Max) を全て0に変更して、インスタンスが全て削除されるのを待ってから、再度ノード数を適当な数に変更してノードを作成し直す
    - `kubectl get nods --watch` で、指定した数のノードが Ready になるまで待機する
- EKS で EBS PV を使えるようにする
    - https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html に書いてあるとおり
    - gp2 を default の Storage Class にしておく

```
kubectl create -f <(cat << EOF
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp2
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
mountOptions:
  - debug
EOF
)

kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
```

## EFS, efs-provisioner

- EFS を作成する
    - VPC: `eksctl` によって作成されたVPCを選択する
    - Create mount targets で Security Groups を `eksctl` によって作成された **ノード** 用のセキュリティグループに変更する
        - こんな感じのやつ: `EKS-fabulous-unicorn-1528645311-DefaultNodeGroup-NodeSecurityGroup-1OC66QBNRBY0J`
        - default は不要なので、外して良い
- EKS で作成した k8s cluster に efs-provisioner を設定する
    - https://github.com/kubernetes-incubator/external-storage/tree/master/aws/efs に書いてあるとおり
    - EKS は RBAC 有効なので Service Account の設定も必要。忘れずに実施する

```
git clone https://github.com/kubernetes-incubator/external-storage
cd external-storage
cd aws/efs
vim deploy/manifest.yaml
git diff
```

```
diff --git a/aws/efs/deploy/manifest.yaml b/aws/efs/deploy/manifest.yaml
index fd36c3d8..70b82f2e 100644
--- a/aws/efs/deploy/manifest.yaml
+++ b/aws/efs/deploy/manifest.yaml
@@ -4,8 +4,8 @@ kind: ConfigMap
metadata:
name: efs-provisioner
data:
-  file.system.id: yourEFSsystemid
-  aws.region: regionyourEFSisin
+  file.system.id: fs-f247245b
+  aws.region: us-west-2
provisioner.name: example.com/aws-efs
---
kind: Deployment
@@ -46,7 +46,7 @@ spec:
    volumes:
        - name: pv-volume
        nfs:
-            server: yourEFSsystemID.efs.yourEFSregion.amazonaws.com
+            server: fs-f247245b.efs.us-west-2.amazonaws.com
            path: /
---
kind: StorageClass
```

```
kubectl create -f deploy/auth/serviceaccount.yaml
kubectl create -f deploy/auth/clusterrole.yaml
kubectl create -f deploy/auth/clusterrolebinding.yaml
kubectl patch deployment efs-provisioner -p '{"spec":{"template":{"spec":{"serviceAccount":"efs-provisioner"}}}}'
```

サンプルとして作成されるPVCを削除しておく

```
kubectl delete pvc efs
```

`kubectl logs` で `efs-provisioner` のログを確認した際に表示される以下のエラーは無視してよい

```
W0611 01:06:08.872993 1 efs-provisioner.go:86] couldn't confirm that the EFS file system exists: AccessDeniedException: User: arn:aws:sts::540305223857:assumed-role/EKS-fabulous-unicorn-1528645311-D-NodeInstanceRole-16115XNK8G8JT/i-0019e5ba334295701 is not authorized to perform: elasticfilesystem:DescribeFileSystems on the specified resource
status code: 403, request id: 9fd8513c-6d13-11e8-9b5f-d338798934b5
```

## Helm

```
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller
```

## MongoDB

- MongoDB 3.6 は StackStorm でサポートされていないので、要注意

```
helm install --name st2-mongodb --set image.tag=3.4 stable/mongodb-replicaset
```

## RabbitMQ

```
helm install --name st2-rabbitmq --set persistentVolume.enabled=true stable/rabbitmq-ha
```

パスワードをメモしておく

```
echo $(kubectl get secret --namespace default st2-rabbitmq-rabbitmq-ha -o jsonpath="{.data.rabbitmq-password}" | base64 --decode)
```

**!!!パスワードにスラッシュが含まれていた場合は、一旦 `helm delete --purge` してやり直す!!!**

パスワードにスラッシュが含まれていると mistral-server が起動できない。mistral-server では設定した URL が oslo_messaging -> kombu の流れで処理されるようだが、どうやら komub に渡る手前の段階で一旦部分的に url decode されるようで、パスワードの部分にスラッシュが含まれていると、それが kombu で url parse する際にホスト部の終わりのスラッシュと間違って解釈されてしまい、結果的にユーザー名がホスト名、パスワードの途中までがポート番号と誤った内容になってしまう。もしかしたら他にも駄目な記号があるかもしれないが、とりあえずバックスラッシュやコロンは入っていても問題ないことを確認済み。

`helm install` する際に自分で使用するユーザー名・パスワードを与えることも出来るので、運に任せるよりは、こちらの方が楽かもしれない。

```
helm install --name st2-rabbitmq --set persistentVolume.enabled=true,rabbitmqUsername=st2,rabbitmqPassword=changeme stable/rabbitmq-ha
```


## Amazon RDS Aurora PostgreSQL

- RDS メニューからウィザードに従って新規に Aurora PostgreSQL インスタンスを作成する
    - Multi-AZ deployment で Create Replica in Different Zone を選択
    - VPC: `eksctl` が作成したものを選択する
    - Public accessibility: no
    - VPC security groups は Choose existing VPC security groups を選択し `eksctl` によって作成された **ノード** 用のセキュリティグループに変更する
        - こんな感じのやつ: `EKS-fabulous-unicorn-1528645311-DefaultNodeGroup-NodeSecurityGroup-1OC66QBNRBY0J`
        - default は不要なので、外して良い
        - EFS を設定したときと同じ
    - rds-launch-wizard という名前のセキュリティグループがウィザードによって新規に作成されるが、これらは不要なので削除してよい
    - Cluster Endpoint をメモしておく
- StackStormで使う DB, user を作成する

```
kubectl run -it psql --image=postgres sh
```

```
RDS_CLUSTER_ENDPOINT=your.cluster.us-west-2.rds.amazonaws.com
RDS_ADMIN_USER=your_admin_user
psql -h $RDS_CLUSTER_ENDPOINT -U $RDS_ADMIN_USER postgres
```

```
create database st2_mistral;
create role st2 with login password 'changeme';
alter database st2_mistral owner to st2;
\q
```

```
exit
```

```
kubectl delete deployment/psql
```

## Amazon ElastiCache Redis

- ElastiCache のメニューからウィザードに従って新規に Redis インスタンスを作成する
    - "Cluster Mode enabled" のチェックは外す。これを使うためにはクライアント側でクラスターモードに対応した接続をする必要がある
    - Node type は t2 family 以外から選択する
        - t2 だと下記 Multi-AZ Auto Failover を有効に出来ない
    - Multi-AZ with Auto Failover にチェックを入れる
    - Subnet group は Create new にする
    - VPC: `eksctl` によって作成されたVPCを選択する
    - Subnets: 3つ全てにチェックを入れる
    - Security Groups を `eksctl` によって作成された **ノード** 用のセキュリティグループに変更する
        - こんな感じのやつ: `EKS-fabulous-unicorn-1528645311-DefaultNodeGroup-NodeSecurityGroup-1OC66QBNRBY0J`
        - default は不要なので、外して良い
        - EFS を設定したときと同じ
    - Primary Endpoint をメモしておく


## StackStorm

- MongoDBのHA接続を実現するため、本家マスターにまだマージされていない機能が実装されたカスタムイメージを使う
- マニフェストファイルも上記に合わせて変更したものを使う

```
git clone https://github.com/shusugmt/st2-docker -b k8s/aws-eks
cd st2-docker
cd runtime/k8s-deployment-examples/aws-eks
vim configmaps.yml
git diff
```

- MongoDB, RabbitMQ, PostgreSQL, Redis の URL を、それぞれ自身の環境に合わせて書き換える
    - MongoDB に関しては helm chart をインストールする際にリリース名を変更していなければ、特に手を加える必要はない
- パスワードに特殊文字が含まれる場合は URL エンコードしたものを使う

```
diff --git a/runtime/k8s-deployment-examples/aws-eks/configmaps.yml b/runtime/k8s-deployment-examples/aws-eks/configmaps.yml
index a8542c3..511e8c6 100644
--- a/runtime/k8s-deployment-examples/aws-eks/configmaps.yml
+++ b/runtime/k8s-deployment-examples/aws-eks/configmaps.yml
@@ -18,7 +18,7 @@ metadata:
   name: st2-externals
 data:
   ST2_MONGODB_URL: mongodb://st2-mongodb-mongodb-replicaset-0.st2-mongodb-mongodb-replicaset:27017,st2-mongodb-mongodb-replicaset-1.st2-mongodb-mongodb-replicaset:27017,st2-mongodb-mongodb-replicaset-2.st2-mongodb-mongodb-replicaset:27017/st2?replicaSet=rs0
-  ST2_RABBITMQ_URL: amqp://st2:changeme@st2-rabbitmq-rabbitmq-ha:5672/
-  ST2_COORDINATION_BACKEND_URL: redis://:@your.redis.cache.amazonaws.com:6379/
-  ST2MISTRAL_RABBITMQ_URL: rabbit://st2:changeme@st2-rabbitmq-rabbitmq-ha:5672/
-  ST2MISTRAL_DB_URL: postgresql+psycopg2://st2:changeme@your.cluster.us-west-2.rds.amazonaws.com:5432/st2_mistral
+  ST2_RABBITMQ_URL: amqp://guest:PP%2Bp24R%3BD%2CR3r%3DF%3F%25x%5D%5D%60%2C.K@st2-rabbitmq-rabbitmq-ha:5672/
+  ST2_COORDINATION_BACKEND_URL: redis://:@st2-redis.mfq0jh.ng.0001.usw2.cache.amazonaws.com:6379/
+  ST2MISTRAL_RABBITMQ_URL: rabbit://guest:PP%2Bp24R%3BD%2CR3r%3DF%3F%25x%5D%5D%60%2C.K@st2-rabbitmq-rabbitmq-ha:5672/
+  ST2MISTRAL_DB_URL: postgresql+psycopg2://st2:changeme@st2-postgres-cluster.cluster-cxjltt8mheoy.us-west-2.rds.amazonaws.com/st2_mistral
```

```
kubectl apply -f configmaps.yml
kubectl apply -f st2.yml
kubectl apply -f init-st2.yml
kubectl get svc
kubectl port-forward svc/st2web 8443:443
```

ブラウザで `https://localhost:8443` を開くと st2web の画面が表示される。ロードバランサー経由でアクセスしたい場合は `kubectl edit svc/st2web` して `type: LoadBalancer` に変更してあげればよい。暫く待つと EXTERNAL IP に FQDN がアサインされるので `kubectl describe svc/st2web` でアドレスを確認出来る。


# クリーンアップ

- StackStorm のリソースを全て `kubectl delete` する
- MongoDB, RabbitMQ を `helm delete --purge` する
    - PVC, PV は消えないので `kubectl delete` を使って削除する
- MongoDB, RabbitMQ の PV として使用されていた EBS ボリュームを削除する
- efs-provisionerを `kubectl delete` する
- RDS, ElastiCache インスタンスを削除する
    - Subnet Group の削除も忘れずに
- EFS インスタンスを削除する
- `eksctl delete cluster`
- 複製した Launch Configuration を削除する


# Notes

- Amazon MQ を RabbitMQ の代わりに使えるか: **No**
    - kombuがAMQP 1.0を喋れるクライアントに対応していないため