Amazon EKSでProxy環境内の マネージドノードグループから外部イメージを利用する

最初に、この記事にたどり着くような要件の場合は、高いセキュリティが必要な環境だと思っています。その場合はインターネット上のレジストリにあるイメージを使うことは本来推奨できるものでは無いと思います。この手順もかなり限定的な用途での利用となるかなと思います。

やりたいこと

プライベートなネットワークでもProxy経由でインターネット上のイメージを利用したい。

プライベートなネットワーク

方法として、原則プライベートで、Proxy経由でのみ外と通信するタイプのネットワークとなります。

プライベートサブネットに構築する、マネージドノードグループにProxyの設定を行いたいと思います。

図に表すと以下の通りです。

f:id:yomon8:20201017141757p:plain

上の図を見ると、プライベートサブネットにエンドポイントが沢山あるのがわかります。要件によってはもっと必要です。

f:id:yomon8:20201016211644p:plain

詳しくはこの辺りに記載があります。

docs.aws.amazon.com

調査

ここまで書いてきたような内容をGoogleで調べると、以下の記事にすぐ当たると思います。

ユーザーデータが投入された Amazon EKS ワーカーノードの HTTP プロキシ設定を自動化する

こちらは非マネージドノード対象ですが、プロキシの自動設定方法が書かれています。

UserData を使うところを調整して使えばマネージドにも応用できるはずです。

環境準備

早速やってみます。

EKSコントロールプレーンのデプロイ

まず、環境をデプロイします。今回はeksctlを利用しています。

eksctl使ってEKSのコントロールプレーンをプライベートクラスタとしてデプロイします。説明の流れ上ノードの定義は抜いておきます。

EKS Fully-Private Cluster - eksctl

CLUSTER_NAME=your-private-cluster
SUBNET_AZ_A=subnet-xxxxx
SUBNET_AZ_C=subnet-xxxxx

cat <<EOF > private-cluster.yml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1

privateCluster:
  enabled: true
  additionalEndpointServices:
   - "autoscaling"
   - "logs"

vpc:
  subnets:
    private:
      asia-northeast-1a: { id: ${SUBNET_AZ_A}}
      asia-northeast-1c: { id: ${SUBNET_AZ_C}}
EOF

eksctlを以下のように実行します。

eksctl create cluster -f private-cluster.yml

※eksctlでPrivate Cluster作ると、最初パブリックで作って、パブリック&プライベートに変更してkubectl設定して、最後にプライベートに設定するので時間かかります。

作成後はSGなどは適宜調整します。

(必要に応じて) kubectlの設定

当初は作成者のみが権限持っているため、ロールに権限を割り当てておきます。

aws eks update-kubeconfig --name your-private-cluster
ADMIN_ROLE_ARN=arn:aws:iam::01234567891012:role/eks-admin

cat <<EOF > role_map.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: ${ADMIN_ROLE_ARN}
      username: adminuser
      groups:
        - system:masters
EOF

kubectl apply -f role_map.yml

マネージドノードグループを追加

コンソール等からマネージドノードグループを追加します。細かい手順は省略します。

f:id:yomon8:20201018104916p:plain

インターネット上のイメージからPodを起動するとエラーとなる

Docker Hubにあるnginxを起動してみると以下のようなエラーになります。

$ kubectl create deployment nginx-app --image=nginx 
deployment.apps/nginx-app created

$ kubectl get po
NAME                      READY   STATUS         RESTARTS   AGE
nginx-app-9964799-mq9cj   0/1     ErrImagePull   0          24s

$ kubectl describe pod nginx-app-9964799-mq9cj
# -- 省略
 Warning  Failed     14s (x2 over 44s)  kubelet            Failed to pull image "nginx": rpc error: code = Unknown desc = Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
# -- 省略

これはネットワーク設計上、当然の結果で、ECR側にあるイメージはVPCエンドポイント経由で問題無く利用できます。

マネージド型ノードグループにProxy設定

プライベートのEKSクラスタができたので、Proxy経由でイメージを取得する設定に入ります。

マネージド型ノードグループに起動テンプレートの UserData を利用することで、Proxyを設定します。

起動テンプレートの設定(Cfn)

Cloudformationで起動テンプレートを作成します。ポイントは UserData の部分なので、他の部分は適宜調整してください。

DockerのサービスにProxy設定を追加しています。

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  ClusterIp:
    Type: String
  ProxyIP:
    Type: String
  ProxyPort:
    Type: Number
  SshKeyPairName:
    Type: String

Resources:
  EKSManagedNodeInPrivateNetworkLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties: 
      LaunchTemplateName: eks-managednodes-in-private-network
      LaunchTemplateData: 
        InstanceType: m3.medium
        KeyName: !Ref SshKeyPairName
        UserData: !Base64 
          "Fn::Sub": |
            Content-Type: multipart/mixed; boundary="==BOUNDARY=="
            MIME-Version:  1.0

            --==BOUNDARY==
            Content-Type: text/cloud-boothook; charset="us-ascii"

            #Set the proxy hostname and port
            PROXY=${ProxyIP}:${ProxyPort}
            MAC=$(curl -s http://169.254.169.254/latest/meta-data/mac/)
            VPC_CIDR=$(curl -s http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MAC/vpc-ipv4-cidr-blocks | xargs | tr ' ' ',')

            #Create the docker systemd directory
            mkdir -p /etc/systemd/system/docker.service.d

            #Configure docker with the proxy
            cloud-init-per instance docker_proxy_config tee <<EOF /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null
            [Service]
            Environment="HTTP_PROXY=http://$PROXY"
            Environment="HTTPS_PROXY=http://$PROXY"
            Environment="NO_PROXY=${ClusterIp},$VPC_CIDR,localhost,127.0.0.1,169.254.169.254,.internal,s3.amazonaws.com,.s3.ap-northeast-1.amazonaws.com,api.ecr.ap-northeast-1.amazonaws.com,dkr.ecr.ap-northeast-1.amazonaws.com,ec2.ap-northeast-1.amazonaws.com,ap-northeast-1.eks.amazonaws.com"
            EOF

            #Reload the daemon and restart docker to reflect proxy configuration at launch of instance
            cloud-init-per instance reload_daemon systemctl daemon-reload 
            cloud-init-per instance enable_docker systemctl enable --now --no-block docker
            --==BOUNDARY==

Cloudformation実行時にPROXYのIP等を入れます。 f:id:yomon8:20201017192423p:plain

マネージドノードグループの立ち上げ

マネージドノードを起動する際に、以下のように起動テンプレートを指定します。

f:id:yomon8:20201017212001p:plain

起動テンプレートを指定したノードグループが立ち上がりました。

f:id:yomon8:20201018105817p:plain

パブリックイメージを使ってPodの起動

今度起動した方のマネージドノードグループ上では、Docker Hubのイメージ使ってnginxが起動できました。

$ kubectl create deployment nginx-app --image=nginx
deployment.apps/nginx-app created

$ kubectl get po
NAME                      READY   STATUS    RESTARTS   AGE
nginx-app-9964799-62xp5   1/1     Running   0          16s

さいごに

最初にも書いたのですが、Proxyで守っているような環境で、パブリックのレジストリからイメージを取るのはセキュリティ的に問題あるのでオススメしませんが、開発環境でのトラブルシュートなど限定的な場面で使える可能性があると思い書いておきました。