AWS_VPC_K8S_CNI_EXTERNALSNATの挙動を確認する

AWS_VPC_K8S_CNI_EXTERNALSNATの理解に苦しんだので、確認したメモ。

コンポーネント バージョン
EKS 1.21
プラットフォームバージョン eks.3
eksctl 0.75.0
aws-node v1.7.5-eksbuild.1

クラスターの作成

1.21でクラスターを作成する。ノードなしで作成する。

CLUSTER_NAME="externalsnat"
cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1
  version: "1.21"
vpc:
  cidr: "10.0.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
EOF
eksctl create cluster -f cluster.yaml

ノードを作成する。

CLUSTER_NAME="externalsnat"
cat << EOF > managed-ng-1.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

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

managedNodeGroups:
  - name: managed-ng-1
    minSize: 1
    maxSize: 1
    desiredCapacity: 1
    privateNetworking: true
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EOF
eksctl create nodegroup -f managed-ng-1.yaml

Adminロールにも権限をつけておく。

CLUSTER_NAME="externalsnat"
USER_NAME="Admin:{{SessionName}}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/Admin"
eksctl create iamidentitymapping --cluster ${CLUSTER_NAME} --arn ${ROLE_ARN} --username ${USER_NAME} --group system:masters

aws-nodeのバージョンはv1.7.5で、AWS_VPC_K8S_CNI_EXTERNALSNATfalseになっている。

$ k -n kube-system get ds aws-node -o yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
(省略)
  name: aws-node
  namespace: kube-system
(省略)
spec:
(省略)
  template:
(省略)
    spec:
(省略)
      containers:
      - env:
        - name: ADDITIONAL_ENI_TAGS
          value: '{}'
        - name: AWS_VPC_CNI_NODE_PORT_SUPPORT
          value: "true"
        - name: AWS_VPC_ENI_MTU
          value: "9001"
        - name: AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER
          value: "false"
        - name: AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG
          value: "false"
        - name: AWS_VPC_K8S_CNI_EXTERNALSNAT
          value: "false"
        - name: AWS_VPC_K8S_CNI_LOGLEVEL
          value: DEBUG
        - name: AWS_VPC_K8S_CNI_LOG_FILE
          value: /host/var/log/aws-routed-eni/ipamd.log
        - name: AWS_VPC_K8S_CNI_RANDOMIZESNAT
          value: prng
        - name: AWS_VPC_K8S_CNI_VETHPREFIX
          value: eni
        - name: AWS_VPC_K8S_PLUGIN_LOG_FILE
          value: /var/log/aws-routed-eni/plugin.log
        - name: AWS_VPC_K8S_PLUGIN_LOG_LEVEL
          value: DEBUG
        - name: DISABLE_INTROSPECTION
          value: "false"
        - name: DISABLE_METRICS
          value: "false"
        - name: ENABLE_POD_ENI
          value: "false"
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: spec.nodeName
        - name: WARM_ENI_TARGET
          value: "1"
        image: 602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon-k8s-cni:v1.7.5-eksbuild.1
        imagePullPolicy: Always
        livenessProbe:
          exec:
            command:
            - /app/grpc-health-probe
            - -addr=:50051
          failureThreshold: 3
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: aws-node
(省略)

検証準備

クラスターを作成したVPC-A内にテスト用のインスタンスA(10.0.64.94)を立てる。

VPC-Bを作成してVPCピアリングをして、インスタンスB(10.1.1.235)を立てる。

セキュリティグループを注意して設定する。

詳細は省略。

検証

まずインスタンスAとインスタンスBからワーカーノードに接続できることを確認する。

ノードを確認する。

$ k get node -o wide
NAME                                              STATUS   ROLES    AGE   VERSION               INTERNAL-IP    EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION                CONTAINER-RUNTIME
ip-10-0-120-188.ap-northeast-1.compute.internal   Ready    <none>   57m   v1.21.5-eks-bc4871b   10.0.120.188   <none>        Amazon Linux 2   5.4.156-83.273.amzn2.x86_64   docker://20.10.7

インスタンスAからワーカーノード(10.0.120.188)にpingSSHをしてみる。接続できる。

[root@ip-10-0-64-94 ~]# ping 10.0.120.188
PING 10.0.120.188 (10.0.120.188) 56(84) bytes of data.
64 bytes from 10.0.120.188: icmp_seq=1 ttl=255 time=1.59 ms
64 bytes from 10.0.120.188: icmp_seq=2 ttl=255 time=1.62 ms
^C
--- 10.0.120.188 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.597/1.610/1.623/0.013 ms
[root@ip-10-0-64-94 ~]# ssh 10.0.120.188
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
[root@ip-10-0-64-94 ~]#

インスタンスBから同じことを試す。接続できる。

[root@ip-10-1-1-235 ~]# ping 10.0.120.188
PING 10.0.120.188 (10.0.120.188) 56(84) bytes of data.
64 bytes from 10.0.120.188: icmp_seq=1 ttl=255 time=0.948 ms
64 bytes from 10.0.120.188: icmp_seq=2 ttl=255 time=0.941 ms
64 bytes from 10.0.120.188: icmp_seq=3 ttl=255 time=0.954 ms
^C
--- 10.0.120.188 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.941/0.947/0.954/0.035 ms
[root@ip-10-1-1-235 ~]# ssh 10.0.120.188
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
[root@ip-10-1-1-235 ~]#

AWS_VPC_K8S_CNI_EXTERNALSNAT=false

NginxのPodを実行する。

$ k create deployment nginx --image=nginx --port 80
deployment.apps/nginx created
$ k get po -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE                                              NOMINATED NODE   READINESS GATES
nginx-7848d4b86f-h6p6v   1/1     Running   0          18s   10.0.102.179   ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>

インスタンスAからPod(10.0.102.179)にpingcurlをしてみる。接続できる。

[root@ip-10-0-64-94 ~]# ping 10.0.102.179
PING 10.0.102.179 (10.0.102.179) 56(84) bytes of data.
64 bytes from 10.0.102.179: icmp_seq=1 ttl=254 time=1.60 ms
64 bytes from 10.0.102.179: icmp_seq=2 ttl=254 time=1.63 ms
64 bytes from 10.0.102.179: icmp_seq=3 ttl=254 time=1.61 ms
^C
--- 10.0.102.179 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.609/1.619/1.632/0.034 ms
[root@ip-10-0-64-94 ~]# curl http://10.0.102.179
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-0-64-94 ~]#

インスタンスBから同じことを試す。接続できる。

[root@ip-10-1-1-235 ~]# ping 10.0.102.179
PING 10.0.102.179 (10.0.102.179) 56(84) bytes of data.
64 bytes from 10.0.102.179: icmp_seq=1 ttl=254 time=0.974 ms
64 bytes from 10.0.102.179: icmp_seq=2 ttl=254 time=0.966 ms
64 bytes from 10.0.102.179: icmp_seq=3 ttl=254 time=0.955 ms
^C
--- 10.0.102.179 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.955/0.965/0.974/0.007 ms
[root@ip-10-1-1-235 ~]# curl http://10.0.102.179
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-1-1-235 ~]#

VPC-Bからだと繋がらないのではと予想していたので、予想と違った。このケースでは、PodのIPの10.0.102.179はプライマリーENI(eth0)のセカンダリーIPを使っていた。

f:id:sotoiwa:20211202163915p:plain

f:id:sotoiwa:20211202163539p:plain

セカンダリーENIのIPを使うPodで試したいので、Podをスケールさせる。

$ k scale deploy nginx --replicas=5
deployment.apps/nginx scaled
$ k get po -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE                                              NOMINATED NODE   READINESS GATES
nginx-7848d4b86f-7b599   1/1     Running   0          5s    10.0.97.245    ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>
nginx-7848d4b86f-c7v5d   1/1     Running   0          5s    10.0.113.95    ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>
nginx-7848d4b86f-kl68s   1/1     Running   0          5s    10.0.102.179   ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>
nginx-7848d4b86f-mgwcz   1/1     Running   0          5s    10.0.101.9     ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>
nginx-7848d4b86f-rfmjt   1/1     Running   0          41s   10.0.100.168   ip-10-0-120-188.ap-northeast-1.compute.internal   <none>           <none>

10.0.97.245セカンダリーENIのセカンダリーIPであることを確認する。このIPを使っているPodに対して先ほどを同じことを試す。

f:id:sotoiwa:20211202163603p:plain

インスタンスAからは接続できる。

[root@ip-10-0-64-94 ~]# ping 10.0.97.245
PING 10.0.97.245 (10.0.97.245) 56(84) bytes of data.
64 bytes from 10.0.97.245: icmp_seq=1 ttl=254 time=2.46 ms
64 bytes from 10.0.97.245: icmp_seq=2 ttl=254 time=1.65 ms
64 bytes from 10.0.97.245: icmp_seq=3 ttl=254 time=1.64 ms
^C
--- 10.0.97.245 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.642/1.921/2.467/0.386 ms
[root@ip-10-0-64-94 ~]# curl http://10.0.97.245
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-0-64-94 ~]#

インスタンスBからは接続できない!

[root@ip-10-1-1-235 ~]# ping 10.0.97.24
PING 10.0.97.24 (10.0.97.24) 56(84) bytes of data.
^C
--- 10.0.97.24 ping statistics ---
9 packets transmitted, 0 received, 100% packet loss, time 8190ms

[root@ip-10-1-1-235 ~]# curl http://10.0.97.24
^C
[root@ip-10-1-1-235 ~]#

PodがSYNが受け取れないのか、あるいは応答のパケットがドロップされてしまうのか、よくわからないがとにかく受け取れない。

逆向きの接続を確認する。

eth0で動作しているPod(10.0.102.179)に入り、sshをインストールする。

k exec -it nginx-7848d4b86f-kl68s -- bash
apt-get update
apt-get install -y ssh

インスタンスA(10.0.64.94)とインスタンスB(10.1.1.235)に接続できるか試す。接続できる。

root@nginx-7848d4b86f-kl68s:/# ssh 10.0.64.94
The authenticity of host '10.0.64.94 (10.0.64.94)' can't be established.
ECDSA key fingerprint is SHA256:jXESh6EUOBjg4lUkLMXAKqNR5b2isnIVGb5q7cYyfDM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.64.94' (ECDSA) to the list of known hosts.
root@10.0.64.94: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-kl68s:/# ssh 10.1.1.235
The authenticity of host '10.1.1.235 (10.1.1.235)' can't be established.
ECDSA key fingerprint is SHA256:WHQiCxG2mqCvMLe0WmHaWkB6WzRLsyXpcgbeEoZ0xDc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.1.1.235' (ECDSA) to the list of known hosts.
root@10.1.1.235: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-kl68s:/#

インスタンスAの/var/log/secureを確認する。PodのIPからきている。

Dec  2 06:43:41 ip-10-0-64-94 sshd[31718]: Connection closed by 10.0.102.179 port 44918 [preauth]

インスタンスBの/var/log/secureを確認する。ノードのIPからきている。つまりSNATされている。

Dec  2 06:43:16 ip-10-1-1-235 sshd[31625]: Connection closed by 10.0.120.188 port 29262 [preauth]

eth1で動作しているPod(10.0.97.245)に入り、sshをインストールする。

k exec -it nginx-7848d4b86f-7b599 -- bash
apt-get update
apt-get install -y ssh

インスタンスA(10.0.64.94)とインスタンスB(10.1.1.235)に接続できるか試す。接続できる。

root@nginx-7848d4b86f-7b599:/# ssh 10.0.64.94
The authenticity of host '10.0.64.94 (10.0.64.94)' can't be established.
ECDSA key fingerprint is SHA256:jXESh6EUOBjg4lUkLMXAKqNR5b2isnIVGb5q7cYyfDM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.64.94' (ECDSA) to the list of known hosts.
root@10.0.64.94: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-7b599:/# ssh 10.1.1.235
The authenticity of host '10.1.1.235 (10.1.1.235)' can't be established.
ECDSA key fingerprint is SHA256:WHQiCxG2mqCvMLe0WmHaWkB6WzRLsyXpcgbeEoZ0xDc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.1.1.235' (ECDSA) to the list of known hosts.
root@10.1.1.235: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-7b599:/#

インスタンスAの/var/log/secureを確認する。PodのIPからきている。

Dec  2 07:02:37 ip-10-0-64-94 sshd[31806]: Connection closed by 10.0.97.245 port 37228 [preauth]

インスタンスBの/var/log/secureを確認する。ノードのIPからきている。つまりSNATされている。

Dec  2 07:02:45 ip-10-1-1-235 sshd[31720]: Connection closed by 10.0.120.188 port 23626 [preauth]

AWS_VPC_K8S_CNI_EXTERNALSNAT=true

設定を変更する。

kubectl set env daemonset -n kube-system aws-node AWS_VPC_K8S_CNI_EXTERNALSNAT=true

aws-nodeが再起動されたことを確認する。

$ k get po -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
aws-node-qxdx4             1/1     Running   0          24s
coredns-76f4967988-85t4h   1/1     Running   0          133m
coredns-76f4967988-cmtl7   1/1     Running   0          133m
kube-proxy-b5lq5           1/1     Running   0          120m

インスタンスAからeth0で稼働するPodにpingcurlをしてみる。接続できる。

[root@ip-10-0-64-94 ~]# ping 10.0.102.179
PING 10.0.102.179 (10.0.102.179) 56(84) bytes of data.
64 bytes from 10.0.102.179: icmp_seq=1 ttl=254 time=1.62 ms
64 bytes from 10.0.102.179: icmp_seq=2 ttl=254 time=1.59 ms
64 bytes from 10.0.102.179: icmp_seq=3 ttl=254 time=1.59 ms
^C
--- 10.0.102.179 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.593/1.602/1.620/0.012 ms
[root@ip-10-0-64-94 ~]# curl http://10.0.102.179
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-0-64-94 ~]#

インスタンスBから同じことを試す。接続できる。

[root@ip-10-1-1-235 ~]# ping 10.0.102.179
PING 10.0.102.179 (10.0.102.179) 56(84) bytes of data.
64 bytes from 10.0.102.179: icmp_seq=1 ttl=254 time=0.971 ms
64 bytes from 10.0.102.179: icmp_seq=2 ttl=254 time=0.971 ms
64 bytes from 10.0.102.179: icmp_seq=3 ttl=254 time=0.960 ms
^C
--- 10.0.102.179 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.960/0.967/0.971/0.025 ms
[root@ip-10-1-1-235 ~]# curl http://10.0.102.179
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-1-1-235 ~]#

インスタンスAからeth1で稼働するPodにpingcurlをしてみる。接続できる。

[root@ip-10-0-64-94 ~]# ping 10.0.97.245
PING 10.0.97.245 (10.0.97.245) 56(84) bytes of data.
64 bytes from 10.0.97.245: icmp_seq=1 ttl=254 time=1.66 ms
64 bytes from 10.0.97.245: icmp_seq=2 ttl=254 time=1.60 ms
64 bytes from 10.0.97.245: icmp_seq=3 ttl=254 time=1.61 ms
^C
--- 10.0.97.245 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.607/1.626/1.662/0.052 ms
[root@ip-10-0-64-94 ~]# curl http://10.0.97.245
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-0-64-94 ~]#

インスタンスBから同じことを試す。接続できるようになった!

[root@ip-10-1-1-235 ~]# ping 10.0.97.245
PING 10.0.97.245 (10.0.97.245) 56(84) bytes of data.
64 bytes from 10.0.97.245: icmp_seq=1 ttl=254 time=0.967 ms
64 bytes from 10.0.97.245: icmp_seq=2 ttl=254 time=0.973 ms
64 bytes from 10.0.97.245: icmp_seq=3 ttl=254 time=0.950 ms
^C
--- 10.0.97.245 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.950/0.963/0.973/0.027 ms
[root@ip-10-1-1-235 ~]# curl http://10.0.97.245
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[root@ip-10-1-1-235 ~]#

eth0で動作しているPod(10.0.102.179)に入り、インスタンスA(10.0.64.94)とインスタンスB(10.1.1.235)に接続できるか試す。接続できる。

$ k exec -it nginx-7848d4b86f-kl68s -- bash
root@nginx-7848d4b86f-kl68s:/# ssh 10.0.64.94
root@10.0.64.94: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-kl68s:/# ssh 10.1.1.235
root@10.1.1.235: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-kl68s:/#

インスタンスAの/var/log/secureを確認する。PodのIPからきている。

Dec  2 07:15:23 ip-10-0-64-94 sshd[31835]: Connection closed by 10.0.102.179 port 34644 [preauth]

インスタンスBの/var/log/secureを確認する。PodのIPからきている。

Dec  2 07:15:38 ip-10-1-1-235 sshd[31786]: Connection closed by 10.0.102.179 port 39066 [preauth]

eth1で動作しているPod(10.0.97.245)に入り、インスタンスA(10.0.64.94)とインスタンスB(10.1.1.235)に接続できるか試す。接続できる。

$ k exec -it nginx-7848d4b86f-7b599 -- bash
root@nginx-7848d4b86f-7b599:/# ssh 10.0.64.94
root@10.0.64.94: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-7b599:/# ssh 10.1.1.235
root@10.1.1.235: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
root@nginx-7848d4b86f-7b599:/#

インスタンスAの/var/log/secureを確認する。PodのIPからきている。

Dec  2 07:18:19 ip-10-0-64-94 sshd[31883]: Connection closed by 10.0.97.245 port 46196 [preauth]

インスタンスBの/var/log/secureを確認する。PodのIPからきている。

Dec  2 07:19:14 ip-10-1-1-235 sshd[31796]: Connection closed by 10.0.97.245 port 50710 [preauth]

まとめ

  • AWS_VPC_K8S_CNI_EXTERNALSNAT=falseの場合、VPC外に出て行くときにSNATが行われる
  • AWS_VPC_K8S_CNI_EXTERNALSNAT=falseの場合、VPC外からセカンダリーENI上で動いているPodに接続できない

AWS_VPC_K8S_CNI_EXTERNALSNAT=falseの場合

接続元 接続先 結果
同じVPCインスタンス プライマリーENI上のPod 接続できる
同じVPCインスタンス セカンダリーENI上のPod 接続できる
ピアリングしたVPCインスタンス プライマリーENI上のPod 接続できる
ピアリングしたVPCインスタンス セカンダリーENI上のPod 接続できない
プライマリーENI上のPod 同じVPCインスタンス 接続できる(SNATされないのでソースはPodのIP)
プライマリーENI上のPod ピアリングしたVPCインスタンス 接続できる(SNATされるのでソースはノードのIP)

AWS_VPC_K8S_CNI_EXTERNALSNAT=trueの場合

接続元 接続先 結果
同じVPCインスタンス プライマリーENI上のPod 接続できる
同じVPCインスタンス セカンダリーENI上のPod 接続できる
ピアリングしたVPCインスタンス プライマリーENI上のPod 接続できる
ピアリングしたVPCインスタンス セカンダリーENI上のPod 接続できる
プライマリーENI上のPod 同じVPCインスタンス 接続できる(SNATされないのでソースはPodのIP)
プライマリーENI上のPod ピアリングしたVPCインスタンス 接続できる(SNATされないのでソースはPodのIP)

Sealed Secretsを試す

Sealed Secretsは数年前に試したことがあるが、もう一度試すメモ。

以前試したときの記事は以下。

コンポーネント バージョン
EKS 1.21
プラットフォームバージョン eks.3
eksctl 0.75.0
Sealed Secrets v0.16.0
Sealed Secrets チャート 1.16.1

クラスターの作成

1.21でクラスターを作成する。ノードなしで作成する。

CLUSTER_NAME="sealedsecrets"
cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1
  version: "1.21"
vpc:
  cidr: "10.0.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
EOF
eksctl create cluster -f cluster.yaml

ノードを作成する。

CLUSTER_NAME="sealedsecrets"
cat << EOF > managed-ng-1.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

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

managedNodeGroups:
  - name: managed-ng-1
    minSize: 2
    maxSize: 10
    desiredCapacity: 2
    privateNetworking: true
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EOF
eksctl create nodegroup -f managed-ng-1.yaml

Adminロールにも権限をつけておく。

CLUSTER_NAME="sealedsecrets"
USER_NAME="Admin:{{SessionName}}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/Admin"
eksctl create iamidentitymapping --cluster ${CLUSTER_NAME} --arn ${ROLE_ARN} --username ${USER_NAME} --group system:masters

Sealed Secretsのデプロイ

Helmチャートでインストールする。

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update
$ helm search repo sealed-secrets
NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                  
sealed-secrets/sealed-secrets   1.16.1          v0.16.0         Helm chart for the sealed-secrets controller.
stable/sealed-secrets           1.12.2          0.13.1          DEPRECATED - A Helm chart for Sealed Secrets 
$ helm inspect values sealed-secrets/sealed-secrets
image:
  repository: quay.io/bitnami/sealed-secrets-controller
  tag: v0.16.0
  pullPolicy: IfNotPresent
  pullSecret: ""

resources: {}
nodeSelector: {}
tolerations: []
affinity: {}

controller:
  # controller.create: `true` if Sealed Secrets controller should be created
  create: true
  # controller.labels: Extra labels to be added to controller deployment
  labels: {}
  # controller.service: Configuration options for controller service
  service:
    # controller.service.labels: Extra labels to be added to controller service
    labels: {}

# namespace: Namespace to deploy the controller.
namespace: ""

serviceAccount:
  # serviceAccount.create: Whether to create a service account or not
  create: true
  # serviceAccount.labels: Extra labels to be added to service account
  labels: {}
  # serviceAccount.name: The name of the service account to create or use
  name: ""

rbac:
  # rbac.create: `true` if rbac resources should be created
  create: true
  # rbac.labels: Extra labels to be added to rbac resources
  labels: {}
  pspEnabled: false

# secretName: The name of the TLS secret containing the key used to encrypt secrets
secretName: "sealed-secrets-key"

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  path: /v1/cert.pem
  hosts:
    - chart-example.local
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

crd:
  # crd.create: `true` if the crd resources should be created
  create: true
  # crd.keep: `true` if the sealed secret CRD should be kept when the chart is deleted
  keep: true

networkPolicy: false

securityContext:
  # securityContext.runAsUser defines under which user the operator Pod and its containers/processes run.
  runAsUser: 1001
  # securityContext.fsGroup defines the filesystem group
  fsGroup: 65534

podAnnotations: {}

podLabels: {}

priorityClassName: ""

serviceMonitor:
  # Enables ServiceMonitor creation for the Prometheus Operator
  create: false
  # How frequently Prometheus should scrape the ServiceMonitor
  interval:
  # Extra labels to apply to the sealed-secrets ServiceMonitor
  labels:
  # The namespace where the ServiceMonitor is deployed, defaults to the installation namespace
  namespace:
  # The timeout after which the scrape is ended
  scrapeTimeout:

dashboards:
  # If enabled, sealed-secrets will create a configmap with a dashboard in json that's going to be picked up by grafana
  # See https://github.com/helm/charts/tree/master/stable/grafana#configuration - `sidecar.dashboards.enabled`
  create: false
  # Extra labels to apply to the dashboard configmaps
  labels:
  # The namespace where the dashboards are deployed, defaults to the installation namespace
  namespace:

デフォルトでインストールする。

$ helm upgrade -i sealed-secrets sealed-secrets/sealed-secrets -n kube-system
Release "sealed-secrets" does not exist. Installing it now.
NAME: sealed-secrets
LAST DEPLOYED: Wed Dec  1 07:17:09 2021
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You should now be able to create sealed secrets.

1. Install client-side tool into /usr/local/bin/

GOOS=$(go env GOOS)
GOARCH=$(go env GOARCH)
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.16.0/kubeseal-$GOOS-$GOARCH
sudo install -m 755 kubeseal-$GOOS-$GOARCH /usr/local/bin/kubeseal

2. Create a sealed secret file

# note the use of `--dry-run` - this does not create a secret in your cluster
kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
 kubeseal \
 --controller-name=sealed-secrets \
 --controller-namespace=kube-system \
 --format [json|yaml] > mysealedsecret.[json|yaml]

The file mysealedsecret.[json|yaml] is a commitable file.

If you would rather not need access to the cluster to generate the sealed secret you can run

kubeseal \
 --controller-name=sealed-secrets \
 --controller-namespace=kube-system \
 --fetch-cert > mycert.pem

to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.

kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
kubeseal \
 --controller-name=sealed-secrets \
 --controller-namespace=kube-system \
 --format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml]

3. Apply the sealed secret

kubectl create -f mysealedsecret.[json|yaml]

Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.

Both the SealedSecret and generated Secret must have the same name and namespace.

Podを確認する。

$ k get po -n kube-system
NAME                              READY   STATUS    RESTARTS   AGE
aws-node-7ksm5                    1/1     Running   0          30m
aws-node-8b76r                    1/1     Running   0          30m
coredns-76f4967988-6jsh9          1/1     Running   0          45m
coredns-76f4967988-dsqcl          1/1     Running   0          45m
kube-proxy-6jv8f                  1/1     Running   0          30m
kube-proxy-zrv6n                  1/1     Running   0          30m
sealed-secrets-7569f57679-jjxwz   1/1     Running   0          3m

Secretを確認する。

$ k get secret -n kube-system --show-labels| grep sealed
sealed-secrets-keyzkf9n                          kubernetes.io/tls                     2      17m   sealedsecrets.bitnami.com/sealed-secrets-key=active
sealed-secrets-token-fflwb                       kubernetes.io/service-account-token   3      17m   <none>
sh.helm.release.v1.sealed-secrets.v1             helm.sh/release.v1                    1      17m   modifiedAt=1638310631,name=sealed-secrets,owner=helm,status=deployed,version=1

sealed-secrets-keyzkf9nがSealing Keyで、30日毎に増えていくと思われる。

Deploymentをeditしてローテーション間隔を1hにする。

$ k -n kube-system edit deploy sealed-secrets

    spec:
      containers:
      - args:
        - --key-prefix
        - sealed-secrets-key
        - --key-renew-period=1h
        command:
        - controller

その後しばらくするとこのように鍵が増えた。

$ k get secret -n kube-system --show-labels| grep sealed
sealed-secrets-keyww7cc                          kubernetes.io/tls                     2      7m58s   sealedsecrets.bitnami.com/sealed-secrets-key=active
sealed-secrets-keyzkf9n                          kubernetes.io/tls                     2      67m     sealedsecrets.bitnami.com/sealed-secrets-key=active
sealed-secrets-token-fflwb                       kubernetes.io/service-account-token   3      68m     <none>
sh.helm.release.v1.sealed-secrets.v1             helm.sh/release.v1                    1      68m     modifiedAt=1638310631,name=sealed-secrets,owner=helm,status=deployed,version=1

Sealed Secretsの作成

ローカルにyamlを作成する。

kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o yaml | \
  kubeseal \
    --controller-name=sealed-secrets \
    --controller-namespace=kube-system \
    --format yaml > mysealedsecret.yaml

これでこんなyamlができた。

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: secret-name
  namespace: default
spec:
  encryptedData:
    foo: AgBYsvMwOApbU62E51gYMlKuu+l32NYl9BJkNA1mvBaxXBLs4DhKbARGaLQCdatZwOXEfCV62Ud9x3M+QSJ/j1JvQPuKV6EzNDjk5ksmFGVP8UrFZBT2xGE+FZKUlwyR1sxpS9aWOFS4J2tZx8gvHy4zTCZ4ROQDf0WkbPRKULMdyIzzGBGD7+TN9j7L5b7U32aL5J1pDGTGnDsWmDS1T/pNtNiNpdsG8h26XVsMXTAjyLoZ+ZTeeA432qvNKPbcmeYrxRWnQl6O4A5jmXNcw2APtlidzM9jRH5Msd0zfkCsywRxfBH5Eo0Yp6EaIxic8Op6W0ojpt/b4/HNo13+zJL/C+BCU6UUHMSRCSRtY983dI+AWzzx681wmE4AwvJRBC7uhoEhEEWxGY6zBCjBA6R+J3PdN9KNEnOLd0GRZcO0T+bXFTn1EDpPhXqre/uAnFdFsM4LvHhi43K5DbwVTal7gN6VloSU0z2xNk/qXbGUcBa5LeJPobYy+tz+D8pXpfB38/JqhKyZCRaZ6jPJiOkEr+PMqo3tn7oTELs8EkwRmvuAblP5SsnOM3xfRD87vSXJ0VnJ1AqeP2SfpHmX6UV1CYlbX5e+wW+2rzmyJckcm9/A1NaQmo8a5Fw/J90bAw0L+eMDB/Glmd8Mx3yGORjAbp771oYDb2ls5vHkLMRhvMY1xWeA0tdRFf4Zem+MIpnbxT0=
  template:
    data: null
    metadata:
      creationTimestamp: null
      name: secret-name
      namespace: default

これをapplyする。

$ k apply -f mysealedsecret.yaml
sealedsecret.bitnami.com/secret-name created

確認する。

$ k get sealedsecret,secret
NAME                                   AGE
sealedsecret.bitnami.com/secret-name   40s

NAME                         TYPE                                  DATA   AGE
secret/default-token-v5tmb   kubernetes.io/service-account-token   3      55m
secret/secret-name           Opaque                                1      40s

他のNamespaceだと復号されないことを確認する。Namespaceを作成する。

$ k create ns test
namespace/test created

先ほどのyamlを編集してNamespaceだけ変える。

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: secret-name
  namespace: test
spec:
  encryptedData:
    foo: AgBYsvMwOApbU62E51gYMlKuu+l32NYl9BJkNA1mvBaxXBLs4DhKbARGaLQCdatZwOXEfCV62Ud9x3M+QSJ/j1JvQPuKV6EzNDjk5ksmFGVP8UrFZBT2xGE+FZKUlwyR1sxpS9aWOFS4J2tZx8gvHy4zTCZ4ROQDf0WkbPRKULMdyIzzGBGD7+TN9j7L5b7U32aL5J1pDGTGnDsWmDS1T/pNtNiNpdsG8h26XVsMXTAjyLoZ+ZTeeA432qvNKPbcmeYrxRWnQl6O4A5jmXNcw2APtlidzM9jRH5Msd0zfkCsywRxfBH5Eo0Yp6EaIxic8Op6W0ojpt/b4/HNo13+zJL/C+BCU6UUHMSRCSRtY983dI+AWzzx681wmE4AwvJRBC7uhoEhEEWxGY6zBCjBA6R+J3PdN9KNEnOLd0GRZcO0T+bXFTn1EDpPhXqre/uAnFdFsM4LvHhi43K5DbwVTal7gN6VloSU0z2xNk/qXbGUcBa5LeJPobYy+tz+D8pXpfB38/JqhKyZCRaZ6jPJiOkEr+PMqo3tn7oTELs8EkwRmvuAblP5SsnOM3xfRD87vSXJ0VnJ1AqeP2SfpHmX6UV1CYlbX5e+wW+2rzmyJckcm9/A1NaQmo8a5Fw/J90bAw0L+eMDB/Glmd8Mx3yGORjAbp771oYDb2ls5vHkLMRhvMY1xWeA0tdRFf4Zem+MIpnbxT0=
  template:
    data: null
    metadata:
      creationTimestamp: null
      name: secret-name
      namespace: test

applyする。

$ k apply -f mysealedsecret-test.yaml
sealedsecret.bitnami.com/secret-name created

この場合は復号されない。

$ k get sealedsecret,secret -n test
NAME                                   AGE
sealedsecret.bitnami.com/secret-name   39s

NAME                         TYPE                                  DATA   AGE
secret/default-token-5tkzz   kubernetes.io/service-account-token   3      2m53s

BuildpacksとJibを試す

以下のブログにしたがってBuildpacksとJibを試してみるメモ。

サンプルアプリケーション

サンプルリポジトリをクローンする。

git clone https://github.com/jamesward/comparing-docker-methods.git
cd comparing-docker-methods

ローカルにインストールされているJavaMavenを確認する。Corretto 8がインストールされているが、MavenはOpenJDK 17を使っている。

$ java -version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment Corretto-8.312.07.1 (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM Corretto-8.312.07.1 (build 25.312-b07, mixed mode)
$ mvn -version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/Cellar/maven/3.8.4/libexec
Java version: 17.0.1, vendor: Homebrew, runtime: /usr/local/Cellar/openjdk/17.0.1/libexec/openjdk.jdk/Contents/Home
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "11.6.1", arch: "x86_64", family: "mac"

Maven Wrapperが入っていなかったので入れる。

mvn -N io.takari:maven:0.7.7:wrapper

アプリケーションを実行する。

./mvnw compile exec:java

アプリケーションにアクセスする。

$ curl localhost:8080
hello, world%

Buildpacks

packコマンドをインストールする。

brew install buildpacks/tap/pack

どのビルダーが使えるのかはサジェストしてくれる。

$ pack builder suggest
Suggested builders:
        Google:                gcr.io/buildpacks/builder:v1      Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python                                                      
        Heroku:                heroku/buildpacks:18              Base builder for Heroku-18 stack, based on ubuntu:18.04 base image                                                                
        Heroku:                heroku/buildpacks:20              Base builder for Heroku-20 stack, based on ubuntu:20.04 base image                                                                
        Paketo Buildpacks:     paketobuildpacks/builder:base     Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Python, Ruby, NGINX and Procfile                        
        Paketo Buildpacks:     paketobuildpacks/builder:full     Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Python, PHP, Ruby, Apache HTTPD, NGINX and Procfile     
        Paketo Buildpacks:     paketobuildpacks/builder:tiny     Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java, Java Native Image and Go                

Tip: Learn more about a specific builder with:
        pack builder inspect <builder-image>

イメージを全部消す。

docker images -aq | xargs docker rmi -f

イメージをビルドする。

$ pack build --builder=gcr.io/buildpacks/builder:v1 comparing-docker-methods:buildpacks
v1: Pulling from buildpacks/builder
...
Digest: sha256:ae9956349502a7d9b340651ddc536e80821c254c77c16f30742216322b3eff87
Status: Downloaded newer image for gcr.io/buildpacks/builder:v1
v1: Pulling from buildpacks/gcp/run
...
Digest: sha256:3f45378eae3d66d960c6e09ea86739aa568744638f90e40b35b26c254d2f3c27
Status: Downloaded newer image for gcr.io/buildpacks/gcp/run:v1
===> DETECTING
4 of 5 buildpacks participating
google.java.runtime    0.9.1
google.java.maven      0.9.0
google.java.entrypoint 0.9.0
google.utils.label     0.0.1
===> ANALYZING
Previous image with name "comparing-docker-methods:buildpacks" not found
Restoring metadata for "google.java.maven:m2" from cache
===> RESTORING
Restoring data for "google.java.maven:m2" from cache
===> BUILDING
=== Java - Runtime (google.java.runtime@0.9.1) ===
Using latest Java 11 runtime version. You can specify a different version with GOOGLE_RUNTIME_VERSION: https://github.com/GoogleCloudPlatform/buildpacks#configuration
--------------------------------------------------------------------------------
Running "curl --fail --show-error --silent --location https://api.adoptopenjdk.net/v3/assets/feature_releases/11/ga?architecture=x64&heap_size=normal&image_type=jdk&jvm_impl=hotspot&os=linux&page=0&page_size=1&project=jdk&sort_order=DESC&vendor=adoptopenjdk"

[
    {
        "binaries": [
            {
                "architecture": "x64",
                "download_count": 166661,
                "heap_size": "normal",
                "image_type": "jdk",
                "jvm_impl": "hotspot",
                "os": "linux",
                "package": {
                    "checksum": "3b1c0c34be4c894e64135a454f2d5aaa4bd10aea04ec2fa0c0efe6bb26528e30",
                    "checksum_link": "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz.sha256.txt",
                    "download_count": 166661,
                    "link": "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz",
                    "metadata_link": "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz.json",
                    "name": "OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz",
                    "size": 192958006
                },
                "project": "jdk",
                "scm_ref": "jdk-11.0.13+8_adopt",
                "updated_at": "2021-10-21T21:55:42Z"
            }
        ],
        "download_count": 508086,
        "id": "MDc6UmVsZWFzZTUxODI0ODA2.DrSSnWfQGA0+fw==",
        "release_link": "https://github.com/adoptium/temurin11-binaries/releases/tag/jdk-11.0.13%2B8",
        "release_name": "jdk-11.0.13+8",
        "release_type": "ga",
        "source": {
            "link": "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-sources_11.0.13_8.tar.gz",
            "name": "OpenJDK11U-sources_11.0.13_8.tar.gz",
            "size": 123491655
        },
        "timestamp": "2021-10-21T21:55:18Z",
        "updated_at": "2021-11-15T16:03:02Z",
        "vendor": "eclipse",
        "version_data": {
            "build": 8,
            "major": 11,
            "minor": 0,
            "openjdk_version": "11.0.13+8",
            "security": 13,
            "semver": "11.0.13+8"
        }
    }
]Done "curl --fail --show-error --silent --location https://api.ado..." (850.3602ms)
Installing Java v11.0.13+8
--------------------------------------------------------------------------------
Running "bash -c curl --fail --show-error --silent --location --retry 3 https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jdk_x64_linux_hotspot_11.0.13_8.tar.gz | tar xz --directory /layers/google.java.runtime/java --strip-components=1"
Done "bash -c curl --fail --show-error --silent --location --retry..." (18.2298941s)
=== Java - Maven (google.java.maven@0.9.0) ===
--------------------------------------------------------------------------------
Running "./mvnw clean package --batch-mode -DskipTests -Dhttp.keepAlive=false --quiet"
Done "./mvnw clean package --batch-mode -DskipTests -Dhttp.keepAli..." (6.8467339s)
=== Java - Entrypoint (google.java.entrypoint@0.9.0) ===
WARNING: Launch layer is setting default=true, but that is not supported until API version 0.6. This setting will be ignored.
Warning: Warning: default processes aren't supported in this buildpack api version. Overriding the default value to false for the following processes: [web]
=== Utils - Label Image (google.utils.label@0.0.1) ===
===> EXPORTING
Adding layer 'google.java.runtime:java'
Adding 1/1 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving comparing-docker-methods:buildpacks...
*** Images (d9a7fa05282b):
      comparing-docker-methods:buildpacks
Reusing cache layer 'google.java.runtime:java'
Reusing cache layer 'google.java.maven:m2'
Successfully built image comparing-docker-methods:buildpacks

イメージを確認する。作成日付がおかしい。

$ docker images
REPOSITORY                  TAG          IMAGE ID       CREATED        SIZE
gcr.io/buildpacks/builder   v1           0be9c8be1372   34 hours ago   626MB
gcr.io/buildpacks/gcp/run   v1           e9c0a00a124f   34 hours ago   120MB
comparing-docker-methods    buildpacks   d9a7fa05282b   41 years ago   444MB

イメージを実行する。

docker run -it -ePORT=8080 -p8080:8080 comparing-docker-methods:buildpacks

アクセス確認する。

$ curl localhost:8080
hello, world%

diveでイメージを見てみる。

dive comparing-docker-methods:buildpacks

f:id:sotoiwa:20211126172335p:plain

ビルド時のメッセージと比較して見ると以下だと思われる。

サイズ 中身
(上の5レイヤー) gcr.io/buildpacks/gcp/run:v1
321 MB Adding layer 'google.java.runtime:java'
177 kB Adding 1/1 app layer(s)
2.5 MB Adding layer 'launcher'
675 B Adding layer 'config'
0 B Adding layer 'process-types'

メッセージからすると、Buildpacksもレイヤリングしてくれそう。

Jib

pom.xmlに以下を追加する。

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>2.6.0</version>
</plugin>

イメージを全部消す。

docker images -aq | xargs docker rmi -f

コンテナイメージをビルドしてローカルに保存する。

$ ./mvnw compile jib:dockerBuild -Dimage=comparing-docker-methods:jib
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.google:sample-java-mvn >---------------------
[INFO] Building sample-java-mvn 0.1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sample-java-mvn ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/sotosugi/workspace/2021/aisac/buildpacks/comparing-docker-methods/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample-java-mvn ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- jib-maven-plugin:2.6.0:dockerBuild (default-cli) @ sample-java-mvn ---
[INFO] 
[INFO] Containerizing application to Docker daemon as comparing-docker-methods:jib...
[WARNING] Base image 'gcr.io/distroless/java:8' does not use a specific image digest - build may not be reproducible
[INFO] Using base image with digest: sha256:34c3598d83f0dba27820323044ebe79e63ad4f137b405676da75a3905a408adf
[INFO] 
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, com.google.WebApp]
[INFO] 
[INFO] Built image to Docker daemon as comparing-docker-methods:jib
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.371 s
[INFO] Finished at: 2021-11-26T16:24:06+09:00
[INFO] ------------------------------------------------------------------------

イメージを確認する。メッセージにでているが、ベースイメージがgcr.io/distroless/java:8なのでサイズが小さい。

$ docker images
REPOSITORY                 TAG       IMAGE ID       CREATED        SIZE
comparing-docker-methods   jib       625395395d3e   51 years ago   130MB

diveで見てみると、最後の前のレイヤーでディレクトリが作られ、最後のレイヤーでjarが置かれているように見える。

f:id:sotoiwa:20211126172359p:plain

Dockerfile

Dockerfileを作成する。このadoptopenjdk/openjdk8のイメージはUbuntuベースのイメージのようだ。

FROM adoptopenjdk/openjdk8 as builder
WORKDIR /app
COPY . /app
RUN ./mvnw compile jar:jar

FROM adoptopenjdk/openjdk8:jre
COPY --from=builder /app/target/*.jar /server.jar
CMD ["java", "-jar", "/server.jar"]

イメージを全部消す。

docker images -aq | xargs docker rmi -f

ビルドする。

docker build -t comparing-docker-methods:dockerfile .

確認する。

$ docker images
REPOSITORY                 TAG          IMAGE ID       CREATED          SIZE
comparing-docker-methods   dockerfile   42980788df0c   46 seconds ago   224MB

GraalVMを使う方法は省略。

まとめ

packとjibでビルドし直し、またベースイメージもpullして、関連したイメージを全部まとめて一覧表示する。

$ docker images
REPOSITORY                  TAG          IMAGE ID       CREATED          SIZE
comparing-docker-methods    dockerfile   42980788df0c   17 minutes ago   224MB
gcr.io/buildpacks/builder   v1           0be9c8be1372   35 hours ago     626MB
gcr.io/buildpacks/gcp/run   v1           e9c0a00a124f   35 hours ago     120MB
adoptopenjdk/openjdk8       jre          4e41c2a5a301   40 hours ago     224MB
adoptopenjdk/openjdk8       latest       f5be1fb5cc77   40 hours ago     320MB
comparing-docker-methods    buildpacks   fb4cfe8a43ed   41 years ago     444MB
comparing-docker-methods    jib          625395395d3e   51 years ago     130MB
gcr.io/distroless/java      8            b762aad6c014   51 years ago     130MB

Security Groups for Podsを試す

今更ながら、Security Groups for Podsを試すメモ。

クラスターの準備

クラスターを作成する。

cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: sgp-cluster
  region: ap-northeast-1

iam:
  withOIDC: true

managedNodeGroups:
  - name: sample-ng
    instanceType: m5.xlarge
    desiredCapacity: 1
    privateNetworking: true
EOF

作成されたVPCを取得する。

VPCID=$(aws eks describe-cluster --name sgp-cluster \
    --query "cluster.resourcesVpcConfig.vpcId" \
    --output text)

クラスターロールにAmazonEKSVPCResourceControllerポリシーが必要だが、eksctlが勝手につけてくれる。

RDSとセキュリティグループの準備

アプリケーション用のセキュリティグループを作成する。

RDSSG=$(aws ec2 create-security-group --group-name RDSDbAccessSG \
    --description "Security group to apply to apps that need access to RDS" --vpc-id $VPCID \
    --query "GroupId" --output text)

データベース用のセキュリティグループを作成する。

f:id:sotoiwa:20211119150803p:plain

PostgreSQLデータベースを作成する。

f:id:sotoiwa:20211119150823p:plain

f:id:sotoiwa:20211119150840p:plain

f:id:sotoiwa:20211119150855p:plain

f:id:sotoiwa:20211119150910p:plain

IAM認証のためのポリシーを作成する。

DBインスタンスの識別子を確認する。

$ aws rds describe-db-instances --query "DBInstances[*].[DBInstanceIdentifier,DbiResourceId]"

[
    [
        "database-1",
        "db-VPBTU3X2JLLL4CFDAV4JQ2KAGY"
    ],
    [
        "production-database",
        "db-YFON4JI4SDJ53PIDJ43JCQN5DU"
    ],
    [
        "staging-database",
        "db-B7LXVCUYPHCIP4TYSALTKMTVYA"
    ]
]

ポリシーを作成する。

DB_ID="db-VPBTU3X2JLLL4CFDAV4JQ2KAGY"
DB_USER="db_userx"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
AWS_REGION=$(aws configure get region)
cat << EOF > db-connect-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-db:connect"
            ],
            "Resource": [
                "arn:aws:rds-db:${AWS_REGION}:${AWS_ACCOUNT_ID}:dbuser:${DB_ID}/${DB_USER}"
            ]
        }
    ]
}
EOF
aws iam create-policy \
    --policy-name db-connect-policy \
    --policy-document file://db-connect-policy.json

データベースアカウントを作成する。

Cloud9を同じVPCに立てて作業する。

データベースに接続したら以下を実行。

CREATE USER db_userx;
GRANT rds_iam TO db_userx;

AWS CNIの設定

aws-nodeのバージョンを確認する。

$ kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
amazon-k8s-cni-init:v1.7.5-eksbuild.1
amazon-k8s-cni:v1.7.5-eksbuild.1

1.8のマニフェストをダウンロードして適用する。

curl -o aws-k8s-cni.yaml https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/release-1.8/config/v1.8/aws-k8s-cni.yaml
sed -i.bak -e 's/us-west-2/ap-northeast-1/' aws-k8s-cni.yaml
kubectl apply -f aws-k8s-cni.yaml

1.9のマニフェストをダウンロードして適用する。

curl -o aws-k8s-cni.yaml https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/release-1.9/config/v1.9/aws-k8s-cni.yaml
sed -i.bak -e 's/us-west-2/ap-northeast-1/' aws-k8s-cni.yaml
kubectl apply -f aws-k8s-cni.yaml

バージョンを確認する。

$ kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
amazon-k8s-cni-init:v1.9.3
amazon-k8s-cni:v1.9.3

この段階でENIは2つになっている。

f:id:sotoiwa:20211119150941p:plain

環境変数を設定する。

kubectl set env daemonset -n kube-system aws-node ENABLE_POD_ENI=true

この時点でENIが増えた。

f:id:sotoiwa:20211119150957p:plain

インターフェースのタイプがtrunkになっている(普通はinterface)。

f:id:sotoiwa:20211119151011p:plain

(その後気がついたらaws-k8s-iのほうは消えてENIは2つだけになっていた)

initContainerの環境変数を変更する。

kubectl edit daemonset aws-node -n kube-system

      initContainers:
      - env:
        - name: DISABLE_TCP_EARLY_DEMUX
          value: "true" # falseをtrueに変更

IRSAの設定

IRSAのための設定ファイルを作成する。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
cat << EOF > serviceaccount.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: sgp-cluster
  region: ap-northeast-1

iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: rds-db-access
      namespace: default
      labels: {role: "backend"}
    attachPolicyARNs:
    - "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/db-connect-policy"
EOF

ServiceAccountを作成する。

eksctl create iamserviceaccount --config-file=serviceaccount.yaml --approve

SecurityGroupPolicyの作成

SecurityGroupPolicyに記載するセキュリティグループを確認する。

CLUSTERSG=$(aws eks describe-cluster --name sgp-cluster \
   --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" \
   --output text)
echo $CLUSTERSG $RDSSG
sg-0178264f1bec3ac39 sg-0900d843812be0618

SecurityGroupPolicyの定義ファイルを作成する。セレクターとしてServiceAccountが使われている。

cat << EOF > sgp-policy.yaml
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: my-sg-policy
spec:
  serviceAccountSelector:
    matchLabels:
      role: backend
  securityGroups:
    groupIds:
      - $CLUSTERSG
      - $RDSSG
EOF

SecurityGroupPolicyを作成する。

$ kubectl apply -f sgp-policy.yaml
securitygrouppolicy.vpcresources.k8s.aws/my-sg-policy created

これでセキュリティグループが増える訳ではなく、新たに作られるPodがセレクターにマッチした場合に指定のセキュリティグループが割り当てられる。

f:id:sotoiwa:20211119151045p:plain

セレクターとしては、PodセレクターとServiceAccountセレクターがある。

$ k explain SecurityGroupPolicy.spec
KIND:     SecurityGroupPolicy
VERSION:  vpcresources.k8s.aws/v1beta1

RESOURCE: spec <Object>

DESCRIPTION:
     SecurityGroupPolicySpec defines the desired state of SecurityGroupPolicy

FIELDS:
   podSelector  <Object>
     A label selector is a label query over a set of resources. The result of
     matchLabels and matchExpressions are ANDed. An empty label selector matches
     all objects. A null label selector matches no objects.

   securityGroups       <Object>
     GroupIds contains the list of security groups that will be applied to the
     network interface of the pod matching the criteria.

   serviceAccountSelector       <Object>
     A label selector is a label query over a set of resources. The result of
     matchLabels and matchExpressions are ANDed. An empty label selector matches
     all objects. A null label selector matches no objects.

イメージのビルド

Pythonのプログラムをpostgres_test_iam.pyとして作成する。

import os

import boto3
import psycopg2

HOST = os.getenv('HOST')
PORT = "5432"
USER = os.getenv('USER')
REGION = "ap-northeast-1"
DBNAME = os.getenv('DATABASE')

session = boto3.Session()
client = boto3.client('rds', region_name=REGION)

token = client.generate_db_auth_token(DBHostname=HOST, Port=PORT, DBUsername=USER, Region=REGION)

conn = None
try:
    conn = psycopg2.connect(host=HOST, port=PORT, database=DBNAME, user=USER, password=token, connect_timeout=3)
    cur = conn.cursor()
    cur.execute("""SELECT version()""")
    query_results = cur.fetchone()
    print(query_results)
    cur.close()
except Exception as e:
    print("Database connection failed due to {}".format(e))
finally:
    if conn is not None:
        conn.close()

Dockerfileを作成する。

FROM python:3.8.5-slim-buster
ADD postgres_test_iam.py /
RUN pip install psycopg2-binary boto3
CMD [ "python", "-u", "./postgres_test_iam.py" ]

イメージをビルドしてECRにプッシュする。

docker build -t postgres-test .
aws ecr create-repository --repository-name postgres-test-demo
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
aws ecr get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag postgres-test ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/postgres-test-demo:latest
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/postgres-test-demo:latest

デプロイ

アプリをデプロイする。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
DB_HOST="database-1.coxgqjvvcbta.ap-northeast-1.rds.amazonaws.com"
DB_NAME="postgres"
DB_USER="db_userx"
cat << EOF > postgres-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test
spec:
  serviceAccountName: rds-db-access
  containers:
  - name: postgres-test
    image: ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: ${DB_HOST}
    - name: DATABASE
      value: ${DB_NAME}
    - name: USER
      value: ${DB_USER}
EOF
$ kubectl apply -f postgres-test.yaml
pod/postgres-test created

ログを確認する。

$ k get po
NAME            READY   STATUS      RESTARTS   AGE
postgres-test   0/1     Completed   1          16s
$ kubectl logs postgres-test
('PostgreSQL 13.3 on aarch64-unknown-linux-gnu, compiled by gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-6), 64-bit',)

ブランチENIが作成されている。

f:id:sotoiwa:20211119151107p:plain

Podを削除する。

$ k delete pod postgres-test
pod "postgres-test" deleted

ブランチENIが消えた。

f:id:sotoiwa:20211119151127p:plain

今度はServiceAccountを指定しないPodを作成する。これによって、ネットワーク的にも、IAM権限的にも繋がらなくなるはず。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
DB_HOST="database-1.coxgqjvvcbta.ap-northeast-1.rds.amazonaws.com"
DB_NAME="postgres"
DB_USER="db_userx"
cat << EOF > postgres-test-no-sa.yaml
apiVersion: v1
kind: Pod
metadata:
  name: postgres-test-no-sa
spec:
  containers:
  - name: postgres-test
    image: ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: ${DB_HOST}
    - name: DATABASE
      value: ${DB_NAME}
    - name: USER
      value: ${DB_USER}
EOF
kubectl apply -f postgres-test-no-sa.yaml

確認する。

$ k get po
NAME                  READY   STATUS      RESTARTS   AGE
postgres-test-no-sa   0/1     Completed   0          5s
$ k logs postgres-test-no-sa
Database connection failed due to connection to server at "database-1.coxgqjvvcbta.ap-northeast-1.rds.amazonaws.com" (192.168.113.177), port 5432 failed: timeout expired

もちろんブランチENIは作成されていない。また、このログはネットワーク的に繋がらなかったエラーであり、generate_db_auth_tokenは成功しているので、このAPIはIAM権限が不要(ローカルで機械的トークンが生成されるだけ?)と思われる。

AMPとAMGを試す

Amazon Managed Service for Prometheus(AMP)とAmazon Managed Service for Grafana(AMG)を試すメモ。

参考資料

クラスターの作成

1.21でクラスターを作成する。ノードなしで作成する。

cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: amp
  region: ap-northeast-1
  version: "1.21"
vpc:
  cidr: "10.0.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
EOF
eksctl create cluster -f cluster.yaml

ノードを作成する。

cat << "EOF" > managed-ng-1.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: amp
  region: ap-northeast-1

managedNodeGroups:
  - name: managed-ng-1
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    privateNetworking: true
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EOF
eksctl create nodegroup -f managed-ng-1.yaml

Adminロールにも管理者権限をつけておく。

CLUSTER_NAME="amp"
USER_NAME="Admin:{{SessionName}}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/Admin"
eksctl create iamidentitymapping --cluster ${CLUSTER_NAME} --arn ${ROLE_ARN} --username ${USER_NAME} --group system:masters

ワークスペースの作成

ワークスペースを作成する。

$ aws amp create-workspace --alias my-first-workspace
{
    "arn": "arn:aws:aps:ap-northeast-1:XXXXXXXXXXXX:workspace/ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX",
    "status": {
        "statusCode": "CREATING"
    },
    "tags": {},
    "workspaceId": "ws-c5f58591-8f13-45df-b652-6e61f1cefa8d"
}

ワークスペースがACTIVEになったことを確認する。

$ aws amp describe-workspace --workspace-id ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX
{
    "workspace": {
        "alias": "my-first-workspace",
        "arn": "arn:aws:aps:ap-northeast-1:XXXXXXXXXXXX:workspace/ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX",
        "createdAt": "2021-09-30T12:49:59.132000+09:00",
        "prometheusEndpoint": "https://aps-workspaces.ap-northeast-1.amazonaws.com/workspaces/ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX/",
        "status": {
            "statusCode": "ACTIVE"
        },
        "tags": {},
        "workspaceId": "ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX"
    }
}

メトリクスの送信

Prometheusから送信する方法と、ADOTコレクターを使用する方法が紹介されている。

ここではPrometheusで試す。

チャートリポジトリを登録する。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics
helm repo update

Namespaceを作成する。

kubectl create namespace prometheus

IRSAをセットアップする。

上記記載がわかりにくいので、eksctlでやる。

Ingest用のポリシーを作成する。

cat << EOF > PermissionPolicyIngest.json
{
  "Version": "2012-10-17",
   "Statement": [
       {"Effect": "Allow",
        "Action": [
           "aps:RemoteWrite",
           "aps:GetSeries",
           "aps:GetLabels",
           "aps:GetMetricMetadata"
        ],
        "Resource": "*"
      }
   ]
}
EOF
aws iam create-policy --policy-name AMPIngestPolicy \
  --policy-document file://PermissionPolicyIngest.json

ロールとServiceAccountを作成する。

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
eksctl create iamserviceaccount \
    --name amp-iamproxy-ingest-service-account \
    --namespace prometheus \
    --cluster amp \
    --attach-policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AMPIngestPolicy \
    --override-existing-serviceaccounts \
    --approve

Helmのvalueを作成する。

WORKSPACE_ID="ws-c5f58591-8f13-45df-b652-XXXXXXXXXXXX"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
AWS_REGION=$(aws configure get region)
ROLE_ARN=$(eksctl get iamserviceaccount --cluster=amp --name amp-iamproxy-ingest-service-account --output json | jq -r '.[].status.roleARN')
cat << EOF > my_prometheus_values.yaml
## The following is a set of default values for prometheus server helm chart which enable remoteWrite to AMP
## For the rest of prometheus helm chart values see: https://github.com/prometheus-community/helm-charts/blob/main/charts/prometheus/values.yaml
##
serviceAccounts:
  server:
    name: amp-iamproxy-ingest-service-account
    annotations:
      eks.amazonaws.com/role-arn: ${ROLE_ARN}
server:
  remoteWrite:
    - url: https://aps-workspaces.${AWS_REGION}.amazonaws.com/workspaces/${WORKSPACE_ID}/api/v1/remote_write
      sigv4:
        region: ${AWS_REGION}
      queue_config:
        max_samples_per_send: 1000
        max_shards: 200
        capacity: 2500
EOF

ServiveAccountはHelmから作るので消しておく。

$ k delete sa -n prometheus amp-iamproxy-ingest-service-account
serviceaccount "amp-iamproxy-ingest-service-account" deleted

デプロイする。

$ helm upgrade --install prometheus prometheus-community/prometheus \
>   -n prometheus \
>   -f my_prometheus_values.yaml
Release "prometheus" does not exist. Installing it now.
NAME: prometheus
LAST DEPLOYED: Thu Sep 30 13:30:41 2021
NAMESPACE: prometheus
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-server.prometheus.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9090


The Prometheus alertmanager can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-alertmanager.prometheus.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=alertmanager" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9093
#################################################################################
######   WARNING: Pod Security Policy has been moved to a global property.  #####
######            use .Values.podSecurityPolicy.enabled with pod-based      #####
######            annotations                                               #####
######            (e.g. .Values.nodeExporter.podSecurityPolicy.annotations) #####
#################################################################################


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prometheus-pushgateway.prometheus.svc.cluster.local


Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

Podを確認する。alertmanagerとかpushgatewayはなくてもよかった。

$ k get po -n prometheus
NAME                                             READY   STATUS    RESTARTS   AGE
prometheus-alertmanager-964c6c598-d8pgn          2/2     Running   0          47s
prometheus-kube-state-metrics-58c5cd6ddb-tkswk   1/1     Running   0          48s
prometheus-node-exporter-jd4js                   1/1     Running   0          47s
prometheus-node-exporter-zhkgt                   1/1     Running   0          48s
prometheus-pushgateway-6cb4d7b8b5-rcwdk          1/1     Running   0          47s
prometheus-server-5cb5b774f7-pc79k               2/2     Running   0          47s

メトリクスのクエリ

マニュアルにはAMPを使った方法がないが、AMPを試してみる。

ワークスペースを作成する。

f:id:sotoiwa:20210930150758p:plain

f:id:sotoiwa:20210930150817p:plain

SSOを別リージョンに設定してあるので、以下のようなメッセージがでた。

f:id:sotoiwa:20210930150832p:plain

AMPとCloudWatchをソースとして設定する。

f:id:sotoiwa:20210930150856p:plain

f:id:sotoiwa:20210930150913p:plain

これで作成する。

f:id:sotoiwa:20210930150936p:plain

f:id:sotoiwa:20210930150953p:plain

ワークスペースにユーザーの割り当てを行う。

f:id:sotoiwa:20210930151012p:plain

f:id:sotoiwa:20210930151337p:plain

SSOでログインしてみると、Grafanaのワークスペースが見えている。

f:id:sotoiwa:20210930151352p:plain

ログインする。

f:id:sotoiwa:20210930151416p:plain

ログインできた。

f:id:sotoiwa:20210930151438p:plain

これだと何もできないので、ユーザーを管理者にしてあげる必要がある。

f:id:sotoiwa:20210930151712p:plain

ボタンが増えた。

f:id:sotoiwa:20210930151926p:plain

データソースを追加する。

f:id:sotoiwa:20210930152149p:plain

f:id:sotoiwa:20210930152046p:plain

ダッシュボードのインポートで3319を指定する。

f:id:sotoiwa:20210930152218p:plain

データソースを指定してインポートを実行する。

f:id:sotoiwa:20210930152256p:plain

それっぽいのができた。

f:id:sotoiwa:20210930152337p:plain

ダッシュボードを作成してパネルを追加してみる。

f:id:sotoiwa:20210930152357p:plain

  • クエリーを指定
    • sum(container_memory_usage_bytes{image!=""}) by (namespace, pod)
  • 凡例のフォーマットを指定
    • {{ namespace }}/{{ pod }}
  • 凡例の場所をRightに指定
  • データの単位をbytes(SI)に指定
  • パネルのタイトルを指定

これでApplyする。さらに追加する。

f:id:sotoiwa:20210930152447p:plain

  • クエリーを指定
    • sum(rate(container_cpu_usage_seconds_total{image!=""}[5m])) by (namespace, pod) * 100
  • 凡例のフォーマットを指定
    • {{ namespace }}/{{ pod }}
  • 凡例の場所をRightに指定
  • データの単位をbytes(SI)に指定
  • パネルのタイトルを指定

これでApplyする。

ごく簡単なダッシュボードができた。

f:id:sotoiwa:20210930152518p:plain

CloudWatchデータソースのほうはデータソースにいくつかダッシュボードが付属してくるようだ。

f:id:sotoiwa:20210930152533p:plain

CloudFront署名付きURLでのファイルのダウンロードおよびアップロード

CloudFrontの署名付きURLを使ったアップロードとダウンロードを試すメモ。

以前はrootユーザーで「CloudFrontのキーペア」を作る必要があったが、IAMユーザーで「信頼されたキーグループ」を利用することができるようになっており、今後はこちらが推奨される。

参考リンク

手順

キーペアの準備

キーペアを作成する。

プライベートキーを作成する。

$ openssl genrsa -out private_key.pem 2048
Generating RSA private key, 2048 bit long modulus
.....................................+++
...+++
e is 65537 (0x10001)

パブリックキーを抽出する。

$ openssl rsa -pubout -in private_key.pem -out public_key.pem
writing RSA key

CloudFrontにパブリックキーをアップロードする。

パブリックキーを作成する。

f:id:sotoiwa:20210927155659p:plain

f:id:sotoiwa:20210927155721p:plain

キーグループを作成する。

f:id:sotoiwa:20210927155748p:plain

f:id:sotoiwa:20210927155808p:plain

ディストリビューションの作成

適当なS3バケットを用意する。

f:id:sotoiwa:20210927160507p:plain

OAIを使ったディストリビューションを作成する。

f:id:sotoiwa:20210927160225p:plain

他はデフォルトで作成する。

適当に置いたファイルにCloudFront経由でアクセスできることを確認する。

$ curl http://d2rn9ssidbwc6t.cloudfront.net/test.txt
Hello World

ディストリビューションの設定変更

ビヘイビアを編集する。

f:id:sotoiwa:20210927160658p:plain

f:id:sotoiwa:20210927160719p:plain

アクセスできなくなったことを確認する。

$ curl http://d2rn9ssidbwc6t.cloudfront.net/test.txt
<?xml version="1.0" encoding="UTF-8"?><Error><Code>MissingKey</Code><Message>Missing Key-Pair-Id query parameter or cookie value</Message></Error>

ダウンロード

PythonでCloudFront署名付きURLを作成する例は以下にある。

venvを作成してbotocoreをインストールする。

python3 -m venv .env
source .env/bin/activate
pip install cryptography botocore

スクリプトを作成する。

generate_url.py

import datetime
import os

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from botocore.signers import CloudFrontSigner


path_to_key = os.getenv('PATH_TO_KEY')
key_id = os.getenv('KEY_ID')
url = os.getenv('URL')
expires_in_seconds = os.getenv('EXPIRES_IN_SECONDS')
expire_date = datetime.datetime.now() - datetime.timedelta(seconds=int(expires_in_seconds))


def rsa_signer(message):
    with open(path_to_key, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
            backend=default_backend()
        )
    return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())

cloudfront_signer = CloudFrontSigner(key_id, rsa_signer)

# Create a signed url that will be valid until the specific expiry date
# provided using a canned policy.
signed_url = cloudfront_signer.generate_presigned_url(
    url, date_less_than=expire_date)
print(signed_url)

スクリプトを実行する。

export PATH_TO_KEY="./private_key.pem"
export KEY_ID="K1GRQBXU1LP9D1"
export URL="https://d2rn9ssidbwc6t.cloudfront.net/test.txt"
export EXPIRES_IN_SECONDS="3600"
URL=$(python generate_url.py)
echo ${URL}
https://d2rn9ssidbwc6t.cloudfront.net/test.txt?Expires=1632752859&Signature=Zd9XVJ8p563S5-H1Fq1KgjWtKzg6Oq2nIumgiYGdDvUJ5pDVS06idJOhUYlQTvAcL~KU3REfX2brISu299f51uPEwyGIFEKKxyayROalin7CoPtOlEEwK06EiBEP8QOJg6K6drTyFzJbtnxQ~vPl6fBOa6TUYUalaQlc3k4LVyU7z3aiX9fVhxX2C~lc9Y2sD8QgPtjn5MWREJcduZcKoMCtGy~3AXohhuZYkWQh7UwCf0PX1ILBiEvMCRUGffR643gIqfepFUT49qjdAqp2FmeVftm~EeqFAmPRjw1OfpBQIUR8Dv0huPpt3D0t3smON38rdq6wZSUpJi5xQgtagg__&Key-Pair-Id=K1GRQBXU1LP9D1

生成されたURLにアクセスする。

curl ${URL}
Hello World

と思ったら、どうやら署名付きURLの生成はAWS CLIでもできた!

aws cloudfront sign --url ${URL} --key-pair-id ${KEY_ID} --private-key file://${PATH_TO_KEY} --date-less-than 2021-9-28

アップロード

アップロードを許可するには、PUTを許可する。(ついでにHTTPSのみにしておく。)

f:id:sotoiwa:20210927160825p:plain

バケットポリシーでOAIからのPutObjectを許可する必要がある。

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EY4QI76AG4Z6G"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::test-XXXXXXXXXXXX/*"
        }
    ]
}

同じスクリプトでURLを生成する。新たにアップロードするファイル用にURLは変える。

export PATH_TO_KEY="./private_key.pem"
export KEY_ID="K1GRQBXU1LP9D1"
export URL="https://d2rn9ssidbwc6t.cloudfront.net/test2.txt"
export EXPIRES_IN_SECONDS="3600"
URL=$(python generate_url.py)

x-amz-content-sha256ヘッダを付与する必要がある。

CloudFront に PUT リクエストを送信してファイルを Amazon S3 バケットにアップロードする場合は、リクエストに x-amz-content-sha256 ヘッダーを追加する必要があります。ヘッダー値にはリクエストの本文の SHA256 ハッシュが含まれている必要があります。詳細については、Amazon Simple Storage Service API リファレンスの「一般的なリクエストヘッダー」ページで x-amz-content-sha256 ヘッダーに関するドキュメントを参照してください。

なお、OAI経由ではPOSTに対応していないため、マルチパートアップロードはできない。

POST リクエストはサポートされません。

オブジェクトの所有者がOAIになるため、x-amz-acl:bucket-owner-full-controlヘッダを付与する必要もある。

FILE="test2.txt"
DIGEST=$(openssl dgst -sha256 -hex < ${FILE} 2>/dev/null | sed 's/^.* //')
curl -D - -XPUT \
    -H "X-Amz-Content-SHA256: ${DIGEST}" \
    -H "x-amz-acl:bucket-owner-full-control" \
    --upload-file ${FILE} ${URL}

これでアップロード成功した。

$ curl -D - -XPUT \
>     -H "X-Amz-Content-SHA256: ${DIGEST}" \
>     -H "x-amz-acl:bucket-owner-full-control" \
>     --upload-file ${FILE} ${URL}
HTTP/2 200 
content-length: 0
date: Mon, 27 Sep 2021 06:31:24 GMT
x-amz-version-id: O5iexVYv2_zgKu4tP046Jpl6TVaIoMA0
x-amz-server-side-encryption: AES256
etag: "cbf41347bb1978f6f32087b2cf01e351"
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 376eb6903adf0a53329357c7636f4f3b.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C1
x-amz-cf-id: gRIqakR-MoVZvijoL9KJAhtG7JDilYLF850ennszVjYDTe3jcwLrkw==

f:id:sotoiwa:20210927161128p:plain

f:id:sotoiwa:20210927161509p:plain