EKSを経由してS3 Static Websiteにアクセスする

## はじめに
静的サイトをサーバレスで公開したいときの選択肢の1つとしてS3のStatic Website Hostingがあります。
今回は独自ドメインでアクセスさせたいときやプライベート空間でアクセスさせたいときに使用できる例としてEKSを経由する方法を紹介します。

概観は次のようになります。ALB Ingress ControllerとNGINX Ingress Controllerを共存させる理由は後述します。

![20200621_eks_s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/6eceb539-26c2-95a5-6c8c-8594d6ebd966.png)



## 環境情報
macOS Mojave 10.14.6
EKS 1.16
Helm 2.14.3

## 手順

ここでは手順を説明していきます。EKSとHelm Tillerは既に構築済みであるものとします。

### 1. S3バケットの作成

今回は`static-website-test01`という名前でS3バケットを作成します。

次にプロパティからStatic Website Hostingの設定を行います。

<img width="1304" alt="スクリーンショット 2020-06-21 22.46.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/5e221f4f-21e9-75c4-4645-a79497d455e2.png">

バケットポリシーはEKSと同一のVPCからのアクセスを許可するようにします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::static-website-test01/*",
            "Condition": {
                "StringEquals": {
                    "aws:sourceVpc": "vpc-XXXXXXXX"
                }
            }
        }
    ]
}
```

またS3バケットに次のファイルをアップロードしておきます。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <title>S3 Static Website</title>
  </head>
  <body>
    <h1>S3 Static Website</h1>
  </body>
</html>
```




### 2. VPC Endpointの作成

AWSコンソールから作成します。
VPCはEKSと同一のものを選択します。ポリシーは特に制限をかけません。

```json
{
    "Statement": [
        {
            "Action": "*",
            "Effect": "Allow",
            "Resource": "*",
            "Principal": "*"
        }
    ]
}
```

### 3. Ingress Controllerのデプロイ

ALB1つで対応するためにALB Ingress ControllerとNGINX Ingress Controllerを用意します。
その際[kube2iam](https://github.com/jtblin/kube2iam)を用いてALB Ingress ControllerがALBを作成できるようにします。
デプロイは[helmfile](https://github.com/roboll/helmfile)を使用します。

```yaml:helmfile.yaml
repositories:
  - name: stable
    url: https://kubernetes-charts.storage.googleapis.com
  - name: incubator
    url: https://kubernetes-charts-incubator.storage.googleapis.com

releases:
  # https://github.com/helm/charts/tree/master/stable/kube2iam
  - name: kube2iam
    namespace: kube-system
    chart: stable/kube2iam
    version: 2.0.1
    wait: true
    values:
      - host:
          iptables: true
          interface: eni+
        extraArgs:
          auto-discover-base-arn: ""
        rbac:
          create: true
  # https://github.com/helm/charts/tree/master/incubator/aws-alb-ingress-controller
  - name: alb-ingress-controller
    namespace: kube-system
    chart: incubator/aws-alb-ingress-controller
    version: 0.1.11
    wait: true
    values:
      - clusterName: <CLUSTER-NAME>
        autoDiscoverAwsRegion: true
        autoDiscoverAwsVpcID: true
        rbac:
          serviceAccountAnnotations: 
            iam.amazonaws.com/role: arn:aws:iam::XXXXXXXXXX:role/alb-ingress-controller
  # https://github.com/helm/charts/tree/master/stable/nginx-ingress
  - name: nginx-ingress
    namespace: kube-system
    chart: stable/nginx-ingress
    version: 1.39.0
    wait: true
    values:
      - controller:
          service:
            type: NodePort
```

次のコマンドでデプロイします。

```console
$ helmfile apply
```

### 4. Ingress, Serviceのデプロイ

ALBを作成するためにALB Ingress Controllerに読み込ませるためのIngressをデプロイします。
`spec.rules[].host`を定義せずに、全リクエストをNGINX Ingress Controllerに飛ばすようにします。
また、annotationsの`alb.ingress.kubernetes.io/scheme`の値はpublicかprivateかで変わってくることに注意しましょう。

```yaml:alb-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    kubernetes.io/ingress.class: alb
  name: alb-ingress
  namespace: kube-system
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: nginx-ingress-controller
          servicePort: 80
```

NGINX Ingress Controllerに読み込ませるIngressを作成します。
ポイントはannotationsに`nginx.ingress.kubernetes.io/upstream-vhost`を付けてホストヘッダを変えることです。
これは、S3バケット毎にIngressリソースが必要になることを示しています。
このことを考慮しALB Ingress ControllerとNGINX Ingress Controllerを組み合わせて、ALBやCLBの複数作成を防いでいます。

```yaml:nginx-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/upstream-vhost: static-website-test01.s3-website-ap-northeast-1.amazonaws.com
  name: nginx-ingress
  namespace: default
spec:
  rules:
  - host: s3.sample.com
    http:
      paths:
      - backend:
          serviceName: s3-static-website
          servicePort: 80
```

ExternalName ServiceでS3バケットのエンドポイントを指定します。

```yaml:externalname-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: s3-static-website
  namespace: default
spec:
  type: ExternalName
  externalName: static-website-test01.s3-website-ap-northeast-1.amazonaws.com
```

次のコマンドでデプロイします。

```console
$ kubectl apply -f alb-ingress.yaml
$ kubectl apply -f nginx-ingress.yaml
$ kubectl apply -f externalname-service.yaml
```

あとは、Route53などで`nginx-ingress.yaml`で定義した`s3.sample.com`の名前解決を行いALBに転送されるよう設定します。

## 確認

試しにブラウザからS3バケットのエンドポイントに直接アクセスしようとしても、公開されていないので拒否されることが確認できます。

<img width="1439" alt="スクリーンショット 2020-06-21 22.50.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/dcad2ce2-d87b-b567-4107-d70b160be6bd.png">

次にIngressで定義した独自ドメインでアクセスします。
`index.html`が表示されることが分かります。

<img width="1438" alt="スクリーンショット 2020-06-22 0.06.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/907baa77-6ec7-c250-9219-f88b5c11ec0f.png">

## まとめ
EKSを経由してS3 Static Websiteにアクセスできることを確認しました。
S3バケットを公開しない方法の1つとして使用できる構成かと思います。
セキュリティを上げるためにNGINX Ingress ControllerにBasic認証を付けるのも良いかもしれません。