Amazon Web Services ブログ
AWS Fargate で Amazon EKS を使用するときにアプリケーションログをキャプチャする方法
Amazon Elastic Kubernetes Service (Amazon EKS) が、AWS Fargate でアプリケーションを実行可能にKubernetes ポッドは EC2 インスタンスをプロビジョニングして管理することなく、実行できます。Fargate は すべてのポッドを VM 分離環境で実行するため、daemonsets
の概念は現在 Fargate には存在しません。したがって、Fargate を使用しているときにアプリケーションログをキャプチャするには、アプリケーションがログを出力する方法と場所を再検討する必要があります。このチュートリアルでは、Fargate で実行されているポッドのアプリケーションログをキャプチャして出荷する方法を示します。
Kubernetes ログ記録アーキテクチャ
最新のアプリケーションを構築するためのゴールドスタンダードを示す Twelve-Factor App マニフェストによると、コンテナ化されたアプリケーションは、ログを stdout
および stderr
に出力する必要があります。これはまた、Kubernetes のベストプラクティスと見なされ、クラスターレベルのログ収集システムはこの前提で構築されています。
Kubernetes ログ記録アーキテクチャは、次の 3 つの異なるレベルを定義します。
- 基本レベルのログ記録: kubectl を使用してポッドログを取得する機能 (例:
kubectl logs myapp
–myapp
はこのクラスターで実行されているポッドです) - ノードレベルのログ記録: コンテナエンジンは、アプリケーションの
stdout
とstderr
からログをキャプチャし、ログファイルに書き込みます。 - クラスターレベルのログ記録: ノードレベルのログ記録に基づく。ログキャプチャエージェントは各ノードで実行されます。エージェントはローカルファイルシステムのログを収集し、Elasticsearch や CloudWatch などの一元化ログの宛先に送信します。エージェントは次の 2 種類のログを収集します。
- ノードのコンテナエンジンによってキャプチャされたコンテナログ。
- システムログ。
Kubernetes 自体は、ログを収集して保存するためのネイティブソリューションを提供していません。ローカルファイルシステムに JSON 形式でログを保存するようにコンテナランタイムを設定します。Docker のようなコンテナランタイムは、コンテナの stdout
と stderr
ストリームをログ記録ドライバーにリダイレクトします。Kubernetes では、コンテナログはノードの /var/log/pods/*.log
に書き込まれます。Kubelet とコンテナランタイムは、systemd のオペレーティングシステムで、独自のログを /var/logs
または journald に書き込みます。次に、Fluentd のようなクラスター全体のログコレクターシステムは、これらのログファイルをノード上でテール
し、ログを出荷して保持できます。このようなログコレクターシステムは通常、ワーカーノードで DaemonSets として実行されます。ただし、DaemonSets を実行することが、Kubernetes でログを集計する唯一の方法ではありません。
一元化されたログ集約システムへのコンテナログの出荷
Kubernetes でログをキャプチャする一般的な方法は 3 つあります。
- Fluentd daemonset のようなノードレベルエージェント。これが推奨パターンです。
- サイドカーコンテナ、Fluentd サイドカーコンテナと同様。
- ログ収集システムに直接書き込む方法。 このアプローチでは、アプリケーションがログの出荷を担当します。Fluentd などのコミュニティビルドソリューションを再利用するのではなく、アプリケーションコードにログ集計システムの SDK を含める必要があるため、これは最も推奨されないオプションです。このパターンは、懸念の分離の原則にも従わないため、ログ記録の実装はアプリケーションから独立している必要があります。そうすることで、アプリケーションに影響を与えたり変更したりすることなく、ログ記録インフラストラクチャを変更できます。
Fargate で実行されているポッドの場合、サイドカーパターンを使用する必要があります。Fluentd (または Fluent Bit) サイドカーコンテナを実行して、アプリケーションによって生成されたログをキャプチャできます。このオプションでは、アプリケーションがログを stdout
または stderr
ではなく、ファイルシステムに書き込む必要があります。このアプローチの結果、kubectl
ログを使用してコンテナログを表示できなくなります。ログを kubectl logs
に表示するには、アプリケーションログを stdout
とファイルシステムの両方に同時に書き込むことができます。以下のチュートリアルでは、ファイルへの tee
書き込みと stdout
を使用しています。
アプリケーションが stdout/stderr にログを記録する場合、Fargate の EKS でクラスターレベルのログをキャプチャするために、アプリケーションに変更を加える必要があるかもしれません。これは望ましくないというお客様からの声があり、アプリケーションのリファクタリングを必要としないソリューションの作成に取り組んでいます。それまでは、EC2 インスタンスを管理せずにワークロードを実行する場合は、サイドカーパターンを使用してクラスターレベルのアプリケーションログをキャプチャできます。基本的なログ記録をポッドレベルでのみキャプチャする必要がある場合、kubectl logs
はアプリケーションのリファクタリングなしで実行できます。
Fargate のポッドは 20GB の一時ストレージを取得します。これは、ポッドに属するすべてのコンテナで使用できます。ローカルファイルシステムにログを書き込むようにアプリケーションを設定し、ログディレクトリ (またはファイル) を監視するように Fluentd に指示できます。Fluentd はログファイルの末尾からイベントを読み取り、CloudWatch などの宛先にイベントを送信して保存します。ログがボリューム全体を占有しないように、ログを定期的にローテーションしてください。
チュートリアル
デモコンテナは、/var/log/containers/application.log
へのログを生成します。Fluentd は、/var/log/containers
を監視し、CloudWatch にログイベントを送信するように設定されています。ポッドは、logrotate サイドカーコンテナも実行して、コンテナログがディスク領域を使い果たしないようにします。この例では、cron は 15 分ごとに logrotate
をトリガーします。環境変数を使用して logrotate の動作をカスタマイズできます。
クラスターと Fargate プロファイルを作成するには、最新バージョンの eksctl が必要です。
以下のコマンドは、EKS クラスターを作成します。kube-system
および default
名前空間のすべてのポッドが Fargate で実行されます。このクラスターには EC2 ノードはありません。
eksctl create cluster \
--name eksfargate-logging-demo \
--fargate
デモアプリケーションを実行する新しい名前空間を作成します。
kubectl create namespace logdemo
logdemo
名前空間の新しい Fargate プロファイルを作成します。これにより、Fargate の logdemo
名前空間でポッドを実行するよう EKS に指示します。
eksctl create fargateprofile \
--namespace logdemo --cluster \
eksfargate-logging-demo \
--name logdemo
クラスターの IAM OIDC ID プロバイダーを作成します。
eksctl utils associate-iam-oidc-provider \
--cluster=eksfargate-logging-demo \
--approve
Fluentd 用の IAM ロールと Kubernetes サービスアカウントを作成します。このロールは、Fluentd コンテナがログイベントを CloudWatch に書き込むことを許可します。
eksctl create iamserviceaccount \
--name logdemo-sa \
--namespace logdemo \
--cluster eksfargate-logging-demo \
--attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
--approve
前のステップで作成したサービスアカウントを確認できます。
kubectl get serviceaccount -n logdemo
NAME SECRETS AGE
default 1 50m
logdemo-sa 1 1m
Fluentd ClusterRole
、RoleBinding
、および ConfigMap
のマニフェストを作成します。
cat <<EOF > fluentd_rbac.yaml
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: fluentd-spring
rules:
- apiGroups: [""]
resources:
- namespaces
- pods
- pods/logs
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: fluentd-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: fluentd-spring
subjects:
- kind: ServiceAccount
name: logdemo-sa
namespace: logdemo
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-spring-config
namespace: logdemo
labels:
k8s-app: fluentd-cloudwatch
data:
fluent.conf: |
@include containers.conf
<match fluent.**>
@type null
</match>
containers.conf: |
<source>
@type tail
@id in_tail_container_logs
@label @containers
path /var/log/containers/*.log
pos_file /usr/local/fluentd-containers.log.pos
tag *
read_from_head true
<parse>
@type none
# @type json
# time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<label @containers>
<filter **>
@type kubernetes_metadata
@id filter_kube_metadata
</filter>
<filter **>
@type record_transformer
@id filter_containers_stream_transformer
<record>
stream_name springlogs #
</record>
</filter>
<filter **>
@type concat
key log
multiline_start_regexp /^\S/
separator ""
flush_interval 5
timeout_label @NORMAL
</filter>
<match **>
@type relabel
@label @NORMAL
</match>
</label>
<label @NORMAL>
<match **>
@type cloudwatch_logs
@id out_cloudwatch_logs_containers
region "#{ENV.fetch('REGION')}"
log_group_name "/aws/containerinsights/#{ENV.fetch('CLUSTER_NAME')}/springapp"
log_stream_name_key stream_name
remove_log_stream_name_key true
auto_create_stream true
<buffer>
flush_interval 5
chunk_limit_size 2m
queued_chunks_limit_size 32
retry_forever true
</buffer>
</match>
</label>
EOF
マニフェストを適用します。
kubectl apply -f fluentd_rbac.yaml
結果は次のようになります。
clusterrole.rbac.authorization.k8s.io/fluentd-spring created
clusterrolebinding.rbac.authorization.k8s.io/fluentd-role-binding created
configmap/fluentd-spring-config created
サンプルアプリケーションのマニフェストを作成します。ポッドには、Fluentd ConfigMap
をコピーして fluentd/etc/
にコピーする initContainer
が含まれています。このディレクトリは、Fluentd コンテナにマウントされます。
cat <<EOF > application_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fargate-log-gen
namespace: logdemo
spec:
replicas: 1
selector:
matchLabels:
app: appf
k8s-app: fluentd-cloudwatch
template:
metadata:
labels:
app: appf
k8s-app: fluentd-cloudwatch
annotations:
iam.amazonaws.com/role: logdemo-sa
spec:
volumes:
- name: fluentdconf
configMap:
name: fluentd-spring-config
- name: app-logs
emptyDir: {}
serviceAccount: logdemo-sa
serviceAccountName: logdemo-sa
containers:
- name: app
image: busybox
command: ['sh', '-c']
args:
- >
while true;
do echo "Time: $(date) $(cat /dev/urandom | tr -dc a-zA-Z0-9 | fold -w 1024 | head -n 1)" | tee -a /var/log/containers/application.log;
sleep 1;
done;
imagePullPolicy: Always
volumeMounts:
- mountPath: /var/log/containers
name: app-logs
resources:
requests:
cpu: 200m
memory: 0.5Gi
limits:
cpu: 400m
memory: 1Gi
securityContext:
privileged: false
readOnlyRootFilesystem: false
allowPrivilegeEscalation: false
- name: logrotate
image: realz/logrotate
volumeMounts:
- mountPath: /var/log/containers
name: app-logs
env:
- name: CRON_EXPR
value: "*/15 * * * *"
- name: LOGROTATE_LOGFILES
value: "/var/log/containers/*.log"
- name: LOGROTATE_FILESIZE
value: "50M"
- name: LOGROTATE_FILENUM
value: "5"
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v1.9.3-debian-cloudwatch-1.0
env:
- name: REGION
value: us-east-2
- name: AWS_REGION
value: us-east-2
- name: CLUSTER_NAME
value: eksfargate-logging-demo
- name: CI_VERSION
value: "k8s/1.0.1"
resources:
limits:
memory: 400Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: fluentdconf
mountPath: /fluentd/etc
- name: app-logs
mountPath: /var/log/containers
EOF
� REGION、AWS_REGION、CLUSTER_NAME の値を環境に合わせて編集します。
コマンドを使用してサンプルアプリケーションをデプロイします。
kubectl apply -f application_deployment.yaml
AWS CLI または CloudWatch コンソールを使用して、書き込まれたログを表示できます。AWS CLI を使用する場合:
aws logs get-log-events \
--log-group-name "/aws/containerinsights/eksfargate-logging-demo/springapp" \
--log-stream-name "springlogs"
デモコンテナによって生成されたログイベントが表示されます。
{
"timestamp": 1593026958792,
"message": "{\"message\":\"Time: Wed Jun 24 19:29:18 UTC 2020=0tfx\"
}",
CloudWatch コンソールで表示するには、ロググループ “/aws/containerinsights/eksfargate-logging-demo/springapp” を検索します。
まとめ
Fargate を使用してポッドを実行する場合は、サイドカーパターンを使用してアプリケーションログをキャプチャする必要があります。kubectl
を使用してログを表示できるように、stdout
とファイルに同時に書き込むことを検討してください。EC2 ノードで実行されているアプリケーションには、daemonset パターンを引き続き使用できます。
Fargate の EKS 向けのアプリケーションログ記録にネイティブソリューションを提供することに取り組んでいます。AWS コンテナロードマップの EKS Fargate 上の FireLens の問題には、検討中の提案が含まれています。コメントをお待ちしております。ご感想をぜひお聞かせください。