【AWS】EKS on Fargateでゲームをデプロイしてみたので解説してみる（with Ingress Controller, 2048）vol.2

# はじめに

以前に前編の記事を書きました。
[【AWS】EKS on Fargateでゲームをデプロイしてみたので解説してみる（with Ingress Controller, 2048）vol.1](https://qiita.com/hesma2/items/b575d52b592350e63d95)

本当は1つの記事にまとめる予定でしたが、
記事が長くなってしまったことと、作成に時間がかかってしまったこと（主に検証）から、やむなく2部制とさせていただいておりました。

前編の記事では、EKSクラスターとFargateプロファイルを作成するところまでを解説しました。
後編では、 `kubectl` コマンドでサービスをデプロイするところを解説していきます。

よろしくお願いします！


# 当記事でのゴール（再掲）
EKS on Fargateを使って、
2048ゲームをデプロイして遊べるようにしたいと思います。

2048とはこのようなゲームです。
きっと皆さん見たこと、遊んだことのあるゲームだと思います。

![2048_play.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/420b02ce-aae0-d4dc-ad72-6a4f2841a531.gif)


# 前提条件（再掲）
- AWSのアカウントを所持している
- macOS
- `eksctl`コマンドではなく、マネジメントコンソールを使用して構築する
    - サービスの理解を深めるため

実際には`eksctl`コマンドで一発でクラスターを作成できたりします。

```
$ eksctl create cluster
```

それでは何もわからないまま終わってしまうので、
一つ一つ解説を入れながらマネジメントコンソールで作っていきたいと思います。

# 全体の流れ（再掲）
1. クラスターの作成
2. Fargateプロファイルの作成
3. `kubectl`コマンドでサービスをデプロイ


# 3. `kubectl`コマンドでサービスをデプロイ

作成したクラスターに向けてサービスをデプロイしていきます。
ここからはAWSマネジメントコンソールではなく、`kubectl`コマンドを中心に使っていきます。

## 3.1.`kubectl`、`awscli`、`eksctl`のインストール

以下のサイトを参照してインストールしてください。
[kubectlのインストールおよびセットアップ](https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/)
[macOS での AWS CLI バージョン 2 のインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-mac.html)
[eksctl の開始方法 ](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-eksctl.html)


## 3.2. `kube.config`にEKSクラスターを登録する

`kubectl`コマンドの向き先を、ローカルではなく前手順で作成したEKSクラスターに変更します。

```shell
$ aws eks --region ap-northeast-1 update-kubeconfig --name eks_handson
```

このようなメッセージが出力されたら成功です。

```shell
Added new context arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson to ~/.kube/config
```

`~/.kube/config`というファイル作成されます。

<details><summary>ファイルの中身</summary><div>

```shell
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxxxxx
    server: https://xxxxxx.gr7.ap-northeast-1.eks.amazonaws.com
  name: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
contexts:
- context:
    cluster: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
    user: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
  name: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
current-context: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-1:xxxxxx:cluster/eks_handson
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - ap-northeast-1
      - eks
      - get-token
      - --cluster-name
      - eks_handson
      command: aws

```

</div></details>

認証が通っているか確認します。

```shell
$ kubectl get svc
```

```shell
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   4m27s
```

## 3.3. デプロイするアプリのマニュフェストを入手する

Kubernetesでは、マニュフェスト（設計書）を使用してコンテナをデプロイしていきます。
どんな`service`に対して、どのような`deployment`を行うのかというのがマニュフェストには定義されています。
`docker-compose.yml`のようなものです。

実際に、`Kompose`という`docker-compose.yml`をKubernetesマニュフェストに変換するサービスも存在します。
次回の記事ではその紹介したいですね。

そのマニュフェストをダウンロードしてきます。

```shell
$ mkdir 2048
$ cd 2048
$ curl -OL https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/2048/2048-deployment.yaml
$ curl -OL https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/2048/2048-service.yaml
$ curl -OL https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/2048/2048-ingress.yaml
```

ダウンロードしてきたマニュフェストを少し編集します。

```shell
# Namespace(名前空間)の変更
$ sed -i -e "s/namespace: \"2048-game\"/namespace: \"handson\"/" ./*.yaml
```

`2048-ingress.yaml`にも1行追加します。
target-typeには`instance`および`ip`の2つのモードがあるのですが、
EKS on Fargateでは`ip`のみでしか動かないようです。
詳細は[Ingress annotations](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/ingress/annotation/)

```shell
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    # 以下の1行を追加
    alb.ingress.kubernetes.io/target-type: ip
```

Namespaceには、手順`2.3`で設定したものを設定します。

Serviceの公開(タイプ)については、下記URLに説明がされています。
[Service - Kubernetes](https://kubernetes.io/ja/docs/concepts/services-networking/service/#publishing-services-service-types)

デフォルトでは静的なポートで公開する設定がされていたので、
ロードバランサーを使用してサービスが公開されるように設定しています。

## 3.4. Ingress Controllerのための設定

ロードバランサーとして、`ALB Ingress Controller`なるものを使用します。
[Amazon EKS の ALB Ingress Controller](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/alb-ingress.html)

基本的に上記URLの手順で設定を行なっていきます。

#### サブネットにタグを追加

- パブリックサブネットに、`kubernetes.io/role/elb` : `1`
- プライベートサブネットに、`kubernetes.io/role/internal-elb` : `1`

を設定していきます。
また、 `kubernetes.io/cluster/eks_handson` タグが設定されていることを確認していください。
EKSクラスター作成時に自動でタグが付けられているはずです。

#### IAMにOIDCプロバイダーを登録

OIDCとはOpenID Connectの略です。
EKSクラスターの認証に使用するIDになります。

EKSクラスター作成時に「OpenID Connect プロバイダー URL」は付与されています。

<img width="360" alt="スクリーンショット 2020-03-30 22.17.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/3409a2b2-44b3-6a74-6034-3ddc7cf8a22c.png">



これを、以下のコマンドを実行することによってIAMに登録します。

```shell
eksctl utils associate-iam-oidc-provider \
    --region ap-northeast-1 \
    --cluster eks_handson \
    --approve
```

マネジメントコンソールのIAMのページを確認すると、IDプロバイダーが作成されています。

<img width="360" alt="スクリーンショット 2020-03-30 22.22.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/affadc8a-d093-c974-2910-9cf1ff558fce.png">

マネジメントコンソールから作成する場合には、

- プロバイダーのタイプ：OpenID Connect
- プロバイダーのURL：EKSクラスターのページからコピペ
- 対象者：sts.amazonaws.com

を設定することで同様のものを作成できます。


#### Ingress ControllerのためのIAMポリシー作成

以下のコマンドを実行することでIAMポリシーが作成されます。

```shell
aws iam create-policy \
    --policy-name ALBIngressControllerIAMPolicy \
    --policy-document https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/iam-policy.json
```

作成したIAMポリシーを持つIAMロールをマネジメントコンソールで作成します。

<details><summary><font color="Red">IAMロールの作成方法</font></summary><div>

---

IAMロール作成画面の信頼されたエンティティから「ウェブID」を選択し、
IDプロバイダーにEKSクラスターを、Audienceにstsを選択します。

<img width="360" alt="スクリーンショット 2020-03-08 18.44.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/4c6560c2-90d4-0961-a75e-126d9a380dd4.png">

ポリシーに先ほど作成した`ALBIngressControllerIAMPolicy`を選択します。

<img width="360" alt="スクリーンショット 2020-03-08 18.48.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/e6820947-e9c0-d499-6789-a4b729aa1628.png">

ロール名を`eks-alb-ingress-controller`として、作成します。

次に信頼関係の編集を行います。

Principal > Federated の箇所にはEKSクラスターのページに記載されている
OpenID Connect プロバイダー URLをコピペします。

Condition > StringEquals の箇所にも編集を加えます。


```shell
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
　　　　　# 以下の行を編集する。
        "Federated": "arn:aws:iam::xxxxxx:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/xxxxxx"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          # 以下の行を編集する。
          # aud -> sub
          # sts.amazonaws.com -> system:serviceaccount:kube-system:alb-ingress-controller
          "oidc.eks.ap-northeast-1.amazonaws.com/id/xxxxxx:sub": "system:serviceaccount:kube-system:alb-ingress-controller"
        }
      }
    }
  ]
}
```

この信頼関係によって、
`alb-ingress-controller` というKubernetesのサービスロールから、
指定のOIDCを持つリソースに対してのみ、
このIAMロールが付与されます。

---

</div></details>


#### Kubernetesのサービスアカウント追加

以下コマンドによって、`alb-ingress-controller` のサービスアカウントを作成します。

```shell
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/rbac-role.yaml
```

追加したサービスアカウントに、IAMロール`eks-alb-ingress-controller`を適用させます。

```shell
kubectl annotate serviceaccount -n kube-system alb-ingress-controller \
eks.amazonaws.com/role-arn=arn:aws:iam::xxxxxxxx:role/eks-alb-ingress-controller
```

#### ALB Ingress Controllerのマニュフェスト入手

ALB Ingress Controllerのマニュフェストをダウンロードしてきます。

```shell
$ cd ..
$ mkdir ingress
$ cd ingress
$ curl -OL https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.4/docs/examples/alb-ingress-controller.yaml
```

マニュフェストを編集し、下記の箇所を変更します。

- クラスター名
- VPCのID
- リージョン

<details><summary>**変更箇所詳細**</summary><div>

```shell
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: alb-ingress-controller
  name: alb-ingress-controller
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: alb-ingress-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: alb-ingress-controller
    spec:
      containers:
        - name: alb-ingress-controller
          args:
            - --ingress-class=alb
            # クラスター名を変更
            - --cluster-name=eks_handson
            # VPCのIDを変更
            - --aws-vpc-id=vpc-xxxxxx
            # リージョンを変更
            - --aws-region=ap-northeast-1
```

</div></details>

## 3.5. CoreDNSのFargate適用

現時点ではデフォルトで作成されるPod、CoreDNSは常にPendingになっていると思います。
これを適用します。

```shell
kubectl patch deployment coredns -n kube-system --type json \
-p='[{"op": "remove", "path": "/spec/template/metadata/annotations/eks.amazonaws.com~1compute-type"}]'
```

少し待つと立ち上がるはずです。

```shell
NAMESPACE     NAME                      READY   STATUS    RESTARTS   AGE
kube-system   coredns-9c47646f9-nbc6k   1/1     Running   0          26h
kube-system   coredns-9c47646f9-vmm42   1/1     Running   0          26h
```



## 3.6. サービスをデプロイする

お待たせしました！
ついにサービスをデプロイしていきます。
その際、Namespaceを先に作成しています。

```shell
# Namespaceの作成
$ kubectl create namespace handson

# デプロイ
$ cd ..
$ kubectl apply -f ingress/
$ kubectl apply -f 2048/
```

デプロイまで少し時間がかかりますので、コーヒーでもすすりながら待ちます。

```shell
$ kubectl get pods -n handson
```

```shell
NAME                              READY   STATUS    RESTARTS   AGE
2048-deployment-7f77b47f7-8hqsw   1/1     Running   0          83s
2048-deployment-7f77b47f7-b9chm   1/1     Running   0          83s
2048-deployment-7f77b47f7-hhvt7   1/1     Running   0          83s
2048-deployment-7f77b47f7-k8m2x   1/1     Running   0          83s
2048-deployment-7f77b47f7-km4d6   1/1     Running   0          83s
```

STATUSがRunningになっていたらデプロイ成功です。
現在は5つのコンテナにスケールされてデプロイされています。

続いて、

```shell
$ kubectl get nodes -n handson
```

```shell
NAME                                                       STATUS   ROLES    AGE    VERSION
fargate-ip-172-31-48-253.ap-northeast-1.compute.internal   Ready    <none>   114s   v1.14.8-eks
fargate-ip-172-31-49-226.ap-northeast-1.compute.internal   Ready    <none>   107s   v1.14.8-eks
fargate-ip-172-31-59-36.ap-northeast-1.compute.internal    Ready    <none>   106s   v1.14.8-eks
fargate-ip-172-31-62-48.ap-northeast-1.compute.internal    Ready    <none>   114s   v1.14.8-eks
fargate-ip-172-31-63-145.ap-northeast-1.compute.internal   Ready    <none>   103s   v1.14.8-eks
```

ノードが5つ立ち上がっていることと、Fargateで立ち上がっていることがわかります。
ノードの名前は自動で作成されるので、そこでFargateなのかEC2なのか判別することができます。

次にサービスが公開されているURLを調べます。
Ingress

```shell
$ kubectl get ingress -n handson
```

```shell
NAMESPACE   NAME           HOSTS   ADDRESS                                   PORTS   AGE
handson     2048-ingress   *       xxxxxx.ap-northeast-1.elb.amazonaws.com   80      2m40s
```

ADDRESSに記載されているURLにアクセスすると、、、

<img width="360" alt="スクリーンショット 2020-03-30 22.45.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533841/2a7c01d4-898d-8fee-0509-73ab68ceb10b.png">

出ました！！
画面にアクセスできるまで少し時間がかかるケースもあります。少し待ってからアクセスしてみましょう。

#### ロードバランサーについて

マネジメントコンソールのEC2のページからロードバランサーを選択すると、
Ingress Controllerによって作成されたロードバランサーを確認できます。

DNS名を確認すると、先ほどアクセスしたURLと同じものになっていると思います。
このロードバランサーに設定されたセキュリティグループも、Ingress Controllerによって作成されたものです。

このセキュリティグループの設定を変えてあげることで、アクセスできるIPアドレスを限定したり、ポートを増やしてあげたりすることができます。


## （おまけ）HTTPSを実装する

今のままだとHTTPのみでセキュリティ的によろしくありません。
HTTPSでもアクセスできるようにする方法も紹介します。
ついてに、HTTPでアクセスしたらHTTPSの方にリダイレクトする設定も行なっていきます。

### 1. 証明書の作成

少し面倒ですが、AWS Certificate Manager上に証明書を作成しなければなりません。
手段は2通りで、

1. マネジメントコンソールから作成（CA作成→証明書作成）
2. オレオレ証明書をCertificate Managerにインポート

前者は若干お金がかかります。
後者の場合、おあつらえのQiita記事があります。
[AWS Certificate Managerでオレオレ証明書をインポートする](https://qiita.com/Raccoon-san/items/f342c4f41c8d6ce465cb)


### 2. Ingressの設定変更

2048-ingress.yamlを編集します。

```shell
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: "2048-ingress"
  namespace: "handson"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    # 以下のannotationを追加。1番上は証明書のARNを記載します。
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:xxxxxx:certificate/xxxxxxxxxx
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect:
      '{
        "Type": "redirect",
        "RedirectConfig": {
          "Protocol": "HTTPS",
          "Port": "443",
          "StatusCode": "HTTP_301"
        }
      }'
  labels:
    app: 2048-ingress
spec:
  rules:
    - http:
        paths:
          # pathを一つ追加。annotationに追加したactionに紐付けます。
          - path: /*
            backend:
              serviceName: ssl-redirect
              servicePort: use-annotation
          - path: /*
            backend:
              serviceName: "service-2048"
              servicePort: 80
```

修正したらデプロイしましょう。

```shell
$ kubectl apply -f 2048
```

再びIngressの情報を取得し、ADDRESSにアクセスすると自動的にHTTPSにリダイレクトされているはずです。
どうぞお試しください。


# まとめ
想像以上に長くなってしまいました。
Ingress Controllerの設定周りでだいぶ苦戦しました。。

KubernetesやEKSを初見で触った時には、
[数時間で完全理解！わりとゴツいKubernetesハンズオン！！](https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d)
の記事に本当にお世話になりました。

図解付きで説明されていて、非常にわかりやすいと感じました。
こちらもぜひ、参考にしてみてください。

ご覧いただき、ありがとうございました。


# 参考
AWS公式

- [AWS Fargate プロファイル](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate-profile.html)
- [ポッド実行ロール](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-execution-role.html)
- [Amazon EKS の ALB Ingress Controller](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/create-service-account-iam-policy-and-role.html#create-service-account-iam-role)
- [サービスアカウントの IAM ロールとポリシーの作成](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/create-service-account-iam-policy-and-role.html#create-service-account-iam-role)

その他記事

- [数時間で完全理解！わりとゴツいKubernetesハンズオン！！](https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d)
- [AWS Certificate Managerでオレオレ証明書をインポートする](https://qiita.com/Raccoon-san/items/f342c4f41c8d6ce465cb)