この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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をデプロイするためのマニュフェストを作成します。
deployment.yaml
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をデプロイするためのマニュフェストを作成します。
lb.yaml
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.
fluentd-daemonset-cloudwatch-rbac.yaml
---
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が東京リージョンにくるのが待ち遠しい。