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 myappmyapp はこのクラスターで実行されているポッドです)
  • ノードレベルのログ記録: コンテナエンジンは、アプリケーションの stdoutstderr からログをキャプチャし、ログファイルに書き込みます。
  • クラスターレベルのログ記録: ノードレベルのログ記録に基づく。ログキャプチャエージェントは各ノードで実行されます。エージェントはローカルファイルシステムのログを収集し、Elasticsearch や CloudWatch などの一元化ログの宛先に送信します。エージェントは次の 2 種類のログを収集します。
    • ノードのコンテナエンジンによってキャプチャされたコンテナログ。
    • システムログ。

Kubernetes 自体は、ログを収集して保存するためのネイティブソリューションを提供していません。ローカルファイルシステムに JSON 形式でログを保存するようにコンテナランタイムを設定します。Docker のようなコンテナランタイムは、コンテナの stdoutstderr ストリームをログ記録ドライバーにリダイレクトします。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 ClusterRoleRoleBinding、および 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 の問題には、検討中の提案が含まれています。コメントをお待ちしております。ご感想をぜひお聞かせください。

Kiran Sahoo

Kiran Sahoo

Kiran Sahoo は、アマゾン ウェブ サービスのシニアビッグデータコンサルタントです。彼は、EKS/EMR などのテクノロジーを活用するお客様を支援しています。彼はニューヨークを拠点としています。