Amazon EKSのコンテナログをCloudWatch Logsに集約する
Amazon EKSにデプロイしたコンテナのログってどうやって管理するんだろ?
本日はそんな課題に対する1つの解決策であるFluentdを利用しコンテナログをCloudWatch Logsに集約する方法を紹介したいと思います。
今回構築する環境
結論から言うと以下を実行すればコンテナログをCloudWatch Logsに集約することができます。
- WorkerノードにCloudWatch Logsへのアクセス権限を付与する
- DaemonSetでFluentdコンテナをデプロイする
ただ、それだけ実行してもログ集約を確認することができないので、今回はEKSクラスタで以下のリソースを作成しました。
- ReplicaSetでNginxコンテナを含むPodを6つ作成
- LoadBalancerServiceでCLBを作成
- DaemonSetでFluentdのPodを作成(ここがメイン)
構成図を見ればパッとわかると思いますが、各Pod(Nginxコンテナ)で出力されるログをFluentdコンテナを利用してCloudWatch Logsに飛ばします。もう少し具体的にいうとこのようなフローになります。
- Nginxコンテナの標準出力および標準エラー出力が、ノードの
/var/log/containers
に出力される - Fluentdコンテナが
/var/log/containers
をtailしログをCloudWatch Logsに送信する
Nginxコンテナのログは明示的に指定しなくてもノードの/var/log/containers
に出力さます。そして、Fluentdコンテナはそのファイルをtailする。。つまり、各コンテナではfluentd logging driverの設定をする必要はありません。素敵デス。
それでは、早速この環境を構築しコンテナログがCloudWatch Logsに集約されることを確認してみましょう!
EKS環境がない方はaws-workshop-for-kubernetes あたりを参考にKubernetesクラスタを構築してみてください。
デプロイ前の環境
今回は2つのサブネットに3つのWorkerノードを配置し、それらをクラスタ化した状態からスタートします。
kubectl
での実行結果は以下のようになっています。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-192-168-124-66.ec2.internal Ready <none> 23s v1.10.3 ip-192-168-141-159.ec2.internal Ready <none> 24s v1.10.3 ip-192-168-149-122.ec2.internal Ready <none> 23s v1.10.3 $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 31m
EKS環境にNginxコンテナをデプロイする
まずは、Nginxコンテナを含むPodをデプロイするためのマニュフェストを作成します。
apiVersion: apps/v1 kind: Deployment metadata: name: deployment spec: replicas: 6 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx-container image: nginx:1.12 ports: - containerPort: 80
kubectl apply
コマンドで、Podをデプロイします。
$ kubectl apply -f deployment.yaml deployment.apps "deployment" created $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE deployment-5bb5496c6d-2cxwm 1/1 Running 0 15s 192.168.149.0 ip-192-168-149-122.ec2.internal deployment-5bb5496c6d-5dsf8 1/1 Running 0 15s 192.168.119.199 ip-192-168-124-66.ec2.internal deployment-5bb5496c6d-8fk2t 1/1 Running 0 15s 192.168.153.84 ip-192-168-141-159.ec2.internal deployment-5bb5496c6d-q2v4j 1/1 Running 0 15s 192.168.102.68 ip-192-168-124-66.ec2.internal deployment-5bb5496c6d-sxnmw 1/1 Running 0 15s 192.168.155.113 ip-192-168-149-122.ec2.internal deployment-5bb5496c6d-t82nk 1/1 Running 0 15s 192.168.173.185 ip-192-168-141-159.ec2.internal
Podが各ノードに2つずつ配置されました。このようなイメージになります。
EKS環境にロードバランサをデプロイする
CLBをデプロイするためのマニュフェストを作成します。
apiVersion: v1 kind: Service metadata: name: lb spec: type: LoadBalancer ports: - name: "http-port" protocol: "TCP" port: 8080 targetPort: 80 nodePort: 30080 selector: app: nginx
kubectl apply
コマンドで、LoadBalancerをデプロイします。
$ kubectl apply -f lb.yaml service "lb" created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 47m lb LoadBalancer 10.100.14.156 a0bf4e0dce68b... 8080:30080/TCP 1m
AWSリソースとしてもCLBが作成されたことを確認することができます。
$ aws elb describe-load-balancers { "LoadBalancerDescriptions": [ { "Subnets": [ "subnet-016a4e2a0846b95ee", "subnet-01817674493758c04", "subnet-0b1750d9b55316554" ], "CanonicalHostedZoneNameID": "Z35SXDOTRQ7X7K", "CanonicalHostedZoneName": "a0bf4e0dce68b11e88c4b0a140bdc4fe-1579511116.us-east-1.elb.amazonaws.com", "ListenerDescriptions": [ { "Listener": { "InstancePort": 30080, "LoadBalancerPort": 8080, "Protocol": "TCP", "InstanceProtocol": "TCP" }, "PolicyNames": [] } ], "HealthCheck": { "HealthyThreshold": 2, "Interval": 10, "Target": "TCP:30080", "Timeout": 5, "UnhealthyThreshold": 6 }, "VPCId": "vpc-0f5f39afb5fc16781", "BackendServerDescriptions": [], "Instances": [ { "InstanceId": "i-0445f1d21cf0e3c68" }, { "InstanceId": "i-0a1ac41a07d210f9f" }, { "InstanceId": "i-0f9e09f0290c2326c" } ], "DNSName": "a0bf4e0dce68b11e88c4b0a140bdc4fe-1579511116.us-east-1.elb.amazonaws.com", "SecurityGroups": [ "sg-0f99a368d876b43d3" ], "Policies": { "LBCookieStickinessPolicies": [], "AppCookieStickinessPolicies": [], "OtherPolicies": [] }, "LoadBalancerName": "a0bf4e0dce68b11e88c4b0a140bdc4fe", "CreatedTime": "2018-11-12T14:55:52.520Z", "AvailabilityZones": [ "us-east-1a", "us-east-1b", "us-east-1c" ], "Scheme": "internet-facing", "SourceSecurityGroup": { "OwnerAlias": "XXXXXXXXXXXX", "GroupName": "k8s-elb-a0bf4e0dce68b11e88c4b0a140bdc4fe" } } ] }
CLB配置後のイメージです。
CLBにアクセスする
CLBで外部からリクエストを受けれる状態になったので、クライアント端末からリクエストを送信してみましょう!
$ curl XXXXXXXXXXXXXXXXXXXXXXX.us-east-1.elb.amazonaws.com:8080 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> 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>
レスポンスが正常に返ってきました。また、接続されたコンテナにてアクセスログを確認することができます。
$ kubectl logs deployment-5bb5496c6d-t82nk 192.168.124.66 - - [12/Nov/2018:15:14:32 +0000] "GET / HTTP/1.1" 200 640 "-" "curl/7.53.1" "-"
IAMロールに権限を追加する
前置きが長くなりましたがここからが本題です。
各ノードからCloudWatch Logsにアクセスする必要があるため、ノードのIAMロールにCloudWatchLogsFullAccess権限を付与します。
EKS環境にFluentdコンテナをデプロイする
Fluentdコンテナを各ノードにデプロイします。公式リポジトリからマニュフェストファイルをクローンしましょう。
git clone https://github.com/fluent/fluentd-kubernetes-daemonset
READMEにも記載がありますが、権限エラーを回避するためFLUENT_UID環境変数に0を設定します。
Run as root
In Kubernetes and default setting, fluentd needs root permission to read logs in /var/log and write pos_file to /var/log. To avoid permission error, you need to set FLUENT_UID >environment variable to 0 in your Kubernetes configuration.
--- apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: fluentd namespace: kube-system rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: fluentd roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-system --- apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: fluentd namespace: kube-system labels: k8s-app: fluentd-logging version: v1 kubernetes.io/cluster-service: "true" spec: template: metadata: annotations: iam.amazonaws.com/role: us-east-1a.staging.kubernetes.ruist.io-service-role labels: k8s-app: fluentd-logging version: v1 kubernetes.io/cluster-service: "true" spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:cloudwatch env: - name: LOG_GROUP_NAME value: "k8s" - name: AWS_REGION value: "us-east-1" - name: FLUENT_UID value: "0" resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers
kubectl apply
コマンドで、DaemonSetをデプロイします。
$ kubectl apply -f fluentd-daemonset-cloudwatch-rbac.yaml serviceaccount "fluentd" created clusterrole.rbac.authorization.k8s.io "fluentd" created clusterrolebinding.rbac.authorization.k8s.io "fluentd" created daemonset.extensions "fluentd" created $ kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE aws-node-9pzlb 1/1 Running 0 1h aws-node-c88hc 1/1 Running 0 1h aws-node-nkh94 1/1 Running 0 1h fluentd-nqd6m 1/1 Running 0 36s fluentd-w4fx9 1/1 Running 0 36s fluentd-x57fs 1/1 Running 0 36s kube-dns-64b69465b4-2x7bm 3/3 Running 0 1h kube-proxy-2l9dt 1/1 Running 0 1h kube-proxy-45xcr 1/1 Running 0 1h kube-proxy-5jljs 1/1 Running 0 1h $ kubectl get daemonset -n kube-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE aws-node 3 3 3 3 3 <none> 1h fluentd 3 3 3 3 3 <none> 53s kube-proxy 3 3 3 3 3 <none> 1h
Fluentdコンテナが各ノードに1つずつ配置されました。このようなイメージになります。
ログ集約を確認する
CloudWatch Logsにログが集約されていることを確認します。何度かロードバランサにアクセスしてみましょう!
$ curl XXXXXXXXXXXXXXXXXXXXXXX.us-east-1.elb.amazonaws.com:8080
しばらく待つと、コンテナ毎にログストリームが作成され。。
各コンテナのログを確認することができました!
最後に
FluentdコンテナをDaemonSetとして配置することで、簡単にコンテナログをCloudWatch Logsに集約することができました!メインのコンテナには手を加えずコンテナログをCloudWatch Logsに集約できるのはとても便利ですね。
あー、EKSが東京リージョンにくるのが待ち遠しい。