ククログ

株式会社クリアコード > ククログ > fluent-plugin-kinesisのIRSA対応をした話

fluent-plugin-kinesisのIRSA対応をした話

はじめに

クリアコードはFluentdの開発に参加しています。 Fluentdのプラグインの中で、AWSのkinesisというサービスに対応するプラグイン(fluent-plugin-kinesis)があります。 このプラグインはEKS(AWSのマネージドk8sサービス)でのPod毎に認証トークンを紐づける仕組み(IRSA)に対応していませんでした。 fluent-plugin-kinesisでIRSAによってサービスへのアクセスをEKS上からできるようにした話を書きます。

IAM Roles for Service Accounts (IRSA)という機能はAWSのIAMという概念が分かっていないと解説するのが難しいのでまずは軽く解説します。 その後に、IRSA対応をFluentdプラグインでやるにはどのようにするといいかを解説します。

IAMとは

IAMとはパスワードやアクセスキーを共有せず、柔軟なアクセス制限をユーザーに対して行う仕組みです。 このIAMを使うことで非rootユーザーを作成し、必要な権限を必要なだけユーザーに付与できます。 S3を参照できるだけのIAMを作成しある非rootユーザーに紐付けると、そのユーザーはS3のバケットのコンテンツしか参照することができない、という状態を作ることができます。

IAM Roles for Service Accounts (IRSA)とは

IAM Roles for Service Accounts (IRSA)という機能はIAMの認証情報をEKS上のPod毎に割り当てられるようにする仕組みです。 1 k8s上でIAMを割り当てる仕組みはkube2iamkiamなどがあります。 kube2iamに関してはワーカーにIAMを割り当てます。 kiamはノード全体に対してIAMを割り当てます。 これらのFLOSSのソリューションに対して、 IRSAではPodに対してIAMを割り当てられるようになっています。 つまり、Pod毎に最小のIAMロールを与えることが可能になります。

EKSクラスターを準備する

IRSAはAWSのEKSクラスターのPod内の認証方法なので、EKSクラスターを立ち上げます。

EKSクラスターの操作に必要なツールの準備

Amazon EKSクラスターの操作にはawsコマンドのインストールとkubernetesの操作を行うためのkubectlのインストールとAWS EKSのクラスターの操作のためのeksctlのインストールがそれぞれ必要です。

aws clikubectleksctlがそれぞれインストールされたものとして認証情報を入れ込んでいきます。

awsコマンドに認証情報を入れるには、

$ aws configure [--profile awesome_profile]

として対話的にアクセスキーID、シークレットを入力します。 defaultプロファイルではないプロファイルを作成して認証情報を作成する際には--profile profile_nameオプションを追加してください。 無事に入力が完了すると、aws configure listを実行すると以下の表示になります

$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************MPLE shared-credentials-file    
secret_key     ****************mPlE shared-credentials-file    
    region           ap-northeast-1      config-file    ~/.aws/config

ここでは東京リージョンをデフォルトリージョンとして設定しています。

EKSクラスターのデプロイ

AWSにEKSクラスターをデプロイしてみます。kinesis-eksという名前のk8s 1.17のクラスターをデプロイします。

$ eksctl create cluster --name kinesis-eks --approve
[ℹ]  eksctl version 0.29.1
[ℹ]  using region ap-northeast-1
[ℹ]  setting availability zones to [ap-northeast-1d ap-northeast-1c ap-northeast-1a]
[ℹ]  subnets for ap-northeast-1d - public:192.168.0.0/19 private:192.168.96.0/19
[ℹ]  subnets for ap-northeast-1c - public:192.168.32.0/19 private:192.168.128.0/19
[ℹ]  subnets for ap-northeast-1a - public:192.168.64.0/19 private:192.168.160.0/19
[ℹ]  nodegroup "ng-cb168d52" will use "ami-0aa15614ef924fd1e" [AmazonLinux2/1.17]
[ℹ]  using Kubernetes version 1.17
[ℹ]  creating EKS cluster "kinesis-eks" in "ap-northeast-1" region with un-managed nodes
[ℹ]  will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup
[ℹ]  if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-1 --cluster=kinesis-eks'
[ℹ]  CloudWatch logging will not be enabled for cluster "kinesis-eks" in "ap-northeast-1"
[ℹ]  you can enable it with 'eksctl utils update-cluster-logging --enable-types={SPECIFY-YOUR-LOG-TYPES-HERE (e.g. all)} --region=ap-northeast-1 --cluster=kinesis-eks'
[ℹ]  Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "kinesis-eks" in "ap-northeast-1"
[ℹ]  2 sequential tasks: { create cluster control plane "kinesis-eks", 2 sequential sub-tasks: { no tasks, create nodegroup "ng-cb168d52" } }
[ℹ]  building cluster stack "eksctl-kinesis-eks-cluster"
[ℹ]  deploying stack "eksctl-kinesis-eks-cluster"
[ℹ]  building nodegroup stack "eksctl-kinesis-eks-nodegroup-ng-cb168d52"
[ℹ]  --nodes-min=2 was set automatically for nodegroup ng-cb168d52
[ℹ]  --nodes-max=2 was set automatically for nodegroup ng-cb168d52
[ℹ]  deploying stack "eksctl-kinesis-eks-nodegroup-ng-cb168d52"
[ℹ]  waiting for the control plane availability...
[✔]  saved kubeconfig as "~/.kube/config"
[ℹ]  no tasks
[✔]  all EKS cluster resources for "kinesis-eks" have been created
[ℹ]  adding identity "arn:aws:iam::788574296432:role/eksctl-kinesis-eks-nodegroup-ng-c-NodeInstanceRole-J3Y9CGYRQ8MB" to auth ConfigMap
[ℹ]  nodegroup "ng-cb168d52" has 0 node(s)
[ℹ]  waiting for at least 2 node(s) to become ready in "ng-cb168d52"
[ℹ]  nodegroup "ng-cb168d52" has 2 node(s)
[ℹ]  node "ip-192-168-53-252.ap-northeast-1.compute.internal" is ready
[ℹ]  node "ip-192-168-6-22.ap-northeast-1.compute.internal" is ready
[ℹ]  kubectl command should work with "~/.kube/config", try 'kubectl get nodes'
[✔]  EKS cluster "kinesis-eks" in "ap-northeast-1" region is ready

数十分待つと、デプロイが完了しました。

OIDCプロバイダーをEKSクラスターにデプロイします。

$ eksctl utils associate-iam-oidc-provider --cluster kinesis-eks --approve
[ℹ]  eksctl version 0.29.1
[ℹ]  using region ap-northeast-1
[ℹ]  will create IAM Open ID Connect provider for cluster "kinesis-eks" in "ap-northeast-1"
[✔]  created IAM Open ID Connect provider for cluster "kinesis-eks" in "ap-northeast-1"

デプロイできました。

IRSAで利用するIAMの認証情報を保持するサービスアカウントもEKSクラスターへデプロイしましょう。 KinesisプラグインのIRSA認証を試すのに、今回はKinesis Data Firehoseというサービスを使ってIRSA認証の動作確認をします。 この記事ではポリシーの作成手順を省略するため、AWS Kinesis Firehoseサービスを全て制御できる既存のポリシーを利用します。

従って、この記事で作成するEKSクラスタに作成するサービスアカウントに紐付けるKinesis Firehoseの権限にはarn:aws:iam::aws:policy/AmazonKinesisFirehoseFullAccessを指定します。 2

$ eksctl create iamserviceaccount --name kinesis-eks-serviceaccount --namespace default --cluster kinesis-eks  --attach-policy-arn arn:aws:iam::aws:policy/AmazonKinesisFirehoseFullAccess --approve 
[ℹ]  eksctl version 0.29.1
[ℹ]  using region ap-northeast-1
[ℹ]  1 existing iamserviceaccount(s) (kube-system/aws-node) will be excluded
[ℹ]  1 iamserviceaccount (default/kinesis-eks-serviceaccount) was included (based on the include/exclude rules)
[ℹ]  1 iamserviceaccount (kube-system/aws-node) was excluded (based on the include/exclude rules)
[!]  serviceaccounts that exists in Kubernetes will be excluded, use --override-existing-serviceaccounts to override
[ℹ]  1 task: { 2 sequential sub-tasks: { create IAM role for serviceaccount "default/kinesis-eks-serviceaccount", create serviceaccount "default/kinesis-eks-serviceaccount" } }
[ℹ]  building iamserviceaccount stack "eksctl-kinesis-eks-addon-iamserviceaccount-default-kinesis-eks-serviceaccount"
[ℹ]  deploying stack "eksctl-kinesis-eks-addon-iamserviceaccount-default-kinesis-eks-serviceaccount"
[ℹ]  created serviceaccount "default/kinesis-eks-serviceaccount"

kubectlでサービスアカウントが作成できているかを確認します。

$ kubectl get serviceaccounts
NAME                         SECRETS   AGE
default                      1         17m
kinesis-eks-serviceaccount   1         3m42s

サービスアカウントが作成できていることが確認できました。

ここまでで、IRSAの動作確認をするためのEKSクラスターを立てることができました。

fluent-plugin-kinesisにIRSA対応を入れる

fluent-plugin-kinesisの開発元には、この記事を執筆している時点ではIRSA対応が入っていません。 AWS SDK Rubyのドキュメントを参照すると、IRSAの認証トークンを処理するクラスはAws::AssumeRoleWebIdentityCredentialsクラスであることがわかります。 つまり、このIRSAを処理するクラスを使って認証情報を取得すると良いことになります。

fluent-plugin-kinesisではAWSの認証情報を取り扱う共通モジュールがあります。 このモジュールにはAws::AssumeRoleWebIdentityCredentialsクラスを扱うコードパスがないため、以下のようにしてそのコードパスを追加します。

diff --git a/lib/fluent/plugin/kinesis_helper/client.rb b/lib/fluent/plugin/kinesis_helper/client.rb
index ba6da75..6870883 100644
--- a/lib/fluent/plugin/kinesis_helper/client.rb
+++ b/lib/fluent/plugin/kinesis_helper/client.rb
@@ -46,6 +46,20 @@ module Fluent
             desc "A URL for a regional STS API endpoint, the default is global"
             config_param :sts_endpoint_url, :string, default: nil
           end
+          # Refer to the following link for additional parameters that could be added:
+          # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/STS/Client.html#assume_role_with_web_identity-instance_method
+          config_section :web_identity_credentials, multi: false do
+            desc "The Amazon Resource Name (ARN) of the role to assume"
+            config_param :role_arn, :string
+            desc "An identifier for the assumed role session"
+            config_param :role_session_name, :string
+            desc "The absolute path to the file on disk containing the OIDC token"
+            config_param :web_identity_token_file, :string, default: nil #required
+            desc "An IAM policy in JSON format"
+            config_param :policy, :string, default: nil
+            desc "The duration, in seconds, of the role session (900-43200)"
+            config_param :duration_seconds, :time, default: nil
+          end
           config_section :instance_profile_credentials, multi: false do
             desc "Number of times to retry when retrieving credentials"
             config_param :retries, :integer, default: nil
@@ -149,6 +163,17 @@ module Fluent
                 credentials_options[:client] = Aws::STS::Client.new(region: @region)
             end
             options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
+          when @web_identity_credentials
+            c = @web_identity_credentials
+            credentials_options[:role_arn] = c.role_arn
+            credentials_options[:role_session_name] = c.role_session_name
+            credentials_options[:web_identity_token_file] = c.web_identity_token_file
+            credentials_options[:policy] = c.policy if c.policy
+            credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
+            if @region
+              credentials_options[:client] = Aws::STS::Client.new(:region => @region)
+            end
+            options[:credentials] = Aws::AssumeRoleWebIdentityCredentials.new(credentials_options)
           when @instance_profile_credentials
             c = @instance_profile_credentials
             credentials_options[:retries] = c.retries if c.retries

これで、以下のように<web_identity_credentials>セクションを記入すると、IRSAによる認証情報がkinesisプラグインに渡されるようになります。

    region ap-northeast-1
    <web_identity_credentials>
      role_arn          "#{ENV['AWS_ROLE_ARN']}"
      role_session_name test-kinesis-session-name
      web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
    </web_identity_credentials>

動作確認用のDockerコンテナを作成する

EKSにPodとして載せるのであれば、Dockerコンテナを作成する必要があります。 Dockerコンテナとしても動くようにするため、設定ファイルは自己完結的にします。

FROM fluent/fluentd:v1.11.3-debian-1.0

LABEL maintainer="Hiroshi Hatake <hatake@clear-code.com>"
USER root
WORKDIR /home/fluent
ENV PATH /fluentd/vendor/bundle/ruby/2.6.0/bin:$PATH
ENV GEM_PATH /fluentd/vendor/bundle/ruby/2.6.0
ENV GEM_HOME /fluentd/vendor/bundle/ruby/2.6.0
# skip runtime bundler installation
ENV FLUENTD_DISABLE_BUNDLER_INJECTION 1

COPY Gemfile* /fluentd/
COPY fluent-plugin-kinesis-*.gem /fluentd/

RUN buildDeps="sudo make gcc g++ libc-dev libffi-dev" \
  runtimeDeps="" \
      && apt-get update \
     && apt-get upgrade -y \
     && apt-get install \
     -y --no-install-recommends \
     $buildDeps $runtimeDeps net-tools \
    && gem install /fluentd/fluent-plugin-kinesis-*.gem --no-document \
    && gem sources --clear-all \
    && SUDO_FORCE_REMOVE=yes \
    apt-get purge -y --auto-remove \
                  -o APT::AutoRemove::RecommendsImportant=false \
                  $buildDeps \
 && rm -rf /var/lib/apt/lists/* \
    && gem sources --clear-all \
    && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

# Copy configuration files
COPY ./conf/fluent.conf /fluentd/etc/
COPY ./conf/conf.d/* /fluentd/etc/conf.d/

# Copy plugins
# COPY plugins /fluentd/plugins/
COPY entrypoint.sh /fluentd/entrypoint.sh

# Environment variables
ENV FLUENTD_OPT=""
ENV FLUENTD_CONF="fluent.conf"

# Overwrite ENTRYPOINT to run fluentd as root for /var/log / /var/lib
ENTRYPOINT ["tini", "--", "/fluentd/entrypoint.sh"]

設定ファイルなどは以下。

entrypoint.sh

Dockerコンテナのエントリポイントのスクリプトを作成します。

#!/usr/bin/env sh

exec fluentd -c /fluentd/etc/${FLUENTD_CONF} -p /fluentd/plugins --gemfile /fluentd/Gemfile ${FLUENTD_OPT}

conf/fluent.conf

@include conf.d/*.conf

<label @mainstream>
  <match **>
    @type kinesis_firehose
    @id out_kinesis_firehose
    region ap-northeast-1
    delivery_stream_name "#{ENV['FIREHOSE_STREAM_NAME'] || fluentd-streams}"
    <web_identity_credentials>
      role_arn          "#{ENV['AWS_ROLE_ARN']}"
      role_session_name test-kinesis-session-name
      web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
    </web_identity_credentials>
    <buffer>
      flush_interval 10s
      chunk_limit_size 1m
      flush_thread_burst_interval 1
      flush_thread_count 2
    </buffer>
  </match>
</label>

conf/conf.d/source.conf

<source>
  @type  forward
  @id    input1
  @label @mainstream
  port   24224
</source>

<source>
  @type sample
  @label @mainstream
  rate 1
  tag raw.sample
</source>

<filter **>
  @type stdout
</filter>

コンテナをビルドします。 今回はテスト用のコンテナを登録するリポジトリをDockerHubに作成しておきました。

$ docker build . -t cosmo0920/fluent-plugin-kinesis-test:latest
Sending build context to Docker daemon  57.86kB
Step 1/17 : FROM fluent/fluentd:v1.11.3-debian-1.0
 ---> 2f58cab1fbc5
Step 2/17 : LABEL maintainer="Hiroshi Hatake <hatake@clear-code.com>"
 ---> Using cache
 ---> e764fb51b05e
Step 3/17 : USER root
 ---> Using cache
 ---> ad1cbc950fc4
Step 4/17 : WORKDIR /home/fluent
 ---> Using cache
 ---> 6ab5210d0cca
Step 5/17 : ENV PATH /fluentd/vendor/bundle/ruby/2.6.0/bin:$PATH
 ---> Using cache
 ---> f73ed93424a7
Step 6/17 : ENV GEM_PATH /fluentd/vendor/bundle/ruby/2.6.0
 ---> Using cache
 ---> 8934937eb22c
Step 7/17 : ENV GEM_HOME /fluentd/vendor/bundle/ruby/2.6.0
 ---> Using cache
 ---> 1576b82df73b
Step 8/17 : ENV FLUENTD_DISABLE_BUNDLER_INJECTION 1
 ---> Using cache
 ---> a7e2b0997fc1
Step 9/17 : COPY Gemfile* /fluentd/
 ---> Using cache
 ---> df851794c5a0
Step 10/17 : COPY fluent-plugin-kinesis-*.gem /fluentd/
 ---> Using cache
 ---> 9e7ad33fb02c
Step 11/17 : RUN buildDeps="sudo make gcc g++ libc-dev libffi-dev"   runtimeDeps=""       && apt-get update      && apt-get upgrade -y      && apt-get install      -y --no-install-recommends      $buildDeps $runtimeDeps net-tools     && gem install /fluentd/fluent-plugin-kinesis-*.gem --no-document     && gem sources --clear-all     && SUDO_FORCE_REMOVE=yes     apt-get purge -y --auto-remove                   -o APT::AutoRemove::RecommendsImportant=false                   $buildDeps  && rm -rf /var/lib/apt/lists/*     && gem sources --clear-all     && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem
 ---> Using cache
 ---> 557e659729b6
Step 12/17 : COPY ./conf/fluent.conf /fluentd/etc/
 ---> 56e9c62c231a
Step 13/17 : COPY ./conf/conf.d/* /fluentd/etc/conf.d/
 ---> a2206c909b34
Step 14/17 : COPY entrypoint.sh /fluentd/entrypoint.sh
 ---> 5d4646527c9a
Step 15/17 : ENV FLUENTD_OPT=""
 ---> Running in 00794bc3a32a
Removing intermediate container 00794bc3a32a
 ---> 6a5a3dc5496b
Step 16/17 : ENV FLUENTD_CONF="fluent.conf"
 ---> Running in aa963bc519be
Removing intermediate container aa963bc519be
 ---> 572c5a904178
Step 17/17 : ENTRYPOINT ["tini", "--", "/fluentd/entrypoint.sh"]
 ---> Running in d68c0af31d16
Removing intermediate container d68c0af31d16
 ---> c765c2784157
Successfully built c765c2784157
Successfully tagged cosmo0920/fluent-plugin-kinesis-test:latest

テスト用のコンテナを登録するリポジトリにビルドしたDockerイメージをプッシュします。

$ docker push cosmo0920/fluent-plugin-kinesis-test:latest
The push refers to repository [docker.io/cosmo0920/fluent-plugin-kinesis-test]
456dfd0e8641: Pushed 
77a5ea4872ea: Pushed 
0270d19e61c5: Pushed 
59b09b04fdb6: Layer already exists 
24cc1822f3a7: Layer already exists 
584adec8072e: Layer already exists 
b836309a29a2: Layer already exists 
6326e503330a: Layer already exists 
781b24f9ba07: Layer already exists 
1302bac58683: Layer already exists 
1563364acccb: Layer already exists 
04cbaaf60ef1: Layer already exists 
2e9de320a378: Layer already exists 
15364b93b273: Layer already exists 
d85310698a88: Layer already exists 
07cab4339852: Layer already exists 
latest: digest: sha256:17c80a7d68941636921fb4e31297b1d37c85a87e1fdf04f6c99c06dd4419f08a size: 3659

EKSクラスタを用いて動作確認する

動作確認に用いたFirehoseのサービスの構成

AWS Kinesis Data Firehoseはストリーム名をfluentd-streams、配信先をS3のfluentd-eks-firehoseバケットに、送信方法はDirect PUT and other sourcesを指定してあります。 今回の記事はIRSA対応を主に解説したい記事のため、設定方法の解説は省略します。

k8sのPodとその周辺の定義ファイルを用意する

k8sではConfigMapにより、Podで使用する設定ファイルなどの内容を差し替える機能があります。 作成したテスト用Dockerfileでも動作確認を行うことは可能ですが、in_sampleからのサンプルイベントを流した時に標準出力でイベントの内容を確認できない状態になっているため、その辺りを修正したConfigMapを定義します。

また、fluent-plugin-kinesisが動作するEKSのPodにfirehoseのIRSAの認証情報をアタッチしたいため、このサービスアカウントの情報もPodの定義ファイルに記入します。

deployment.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: default
  labels:
    k8s-app: fluentd-logging
    version: v1
data:
  fluent.conf: |
    <source>
      @type  forward
      @id    input1
      @label @mainstream
      port   24224
    </source>

    <source>
      @type sample
      @label @mainstream
      rate 1
      tag raw.sample
    </source>

    <label @mainstream>
      <filter **>
        @type stdout
      </filter>

      <match **>
        @type kinesis_firehose
        @id out_kinesis_firehose
        region ap-northeast-1
        delivery_stream_name fluentd-streams
        <web_identity_credentials>
          role_arn          "#{ENV['AWS_ROLE_ARN']}"
          role_session_name test-kinesis-session-name
          web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
        </web_identity_credentials>
        <buffer>
          flush_interval 10s
          chunk_limit_size 1m
          flush_thread_burst_interval 1
          flush_thread_count 2
        </buffer>
      </match>
    </label>
---
apiVersion: v1
kind: Pod
metadata:
  name: fluentd-kinesis-test
  namespace: default
  labels:
    k8s-app: fluentd-logging
    version: v1
spec:
  serviceAccountName: kinesis-eks-serviceaccount
  containers:
  - image: docker.io/cosmo0920/fluent-plugin-kinesis-test:latest
    name: fluentd-kinesis-test
    volumeMounts:
    - name: config-volume
      mountPath: /fluentd/etc
  volumes:
  - name: config-volume
    configMap:
      name: fluentd-config

EKSクラスタにデプロイします。

$ kubectl apply -f deployment.yaml
configmap/fluentd-config created
pod/fluentd-kinesis-test created

デプロイした直後はコンテナが作成されているという表示になります。

$ kubectl get pods
NAME                   READY   STATUS              RESTARTS   AGE
fluentd-kinesis-test   0/1     ContainerCreating   0          10s

しばらく経つと、STATUSRunningに変わります。

$ kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
fluentd-kinesis-test   1/1     Running   0          14s

fluent-kinesis-testという名前のPodのログを取得してみます。

$ kubectl logs fluentd-kinesis-test
2020-10-15 08:21:16 +0000 [info]: parsing config file is succeeded path="/fluentd/etc/fluent.conf"
2020-10-15 08:21:16 +0000 [info]: gem 'fluent-plugin-kinesis' version '3.2.3'
2020-10-15 08:21:16 +0000 [info]: gem 'fluentd' version '1.11.4'
2020-10-15 08:21:16 +0000 [warn]: both of Plugin @id and path for <storage> are not specified. Using on-memory store.
2020-10-15 08:21:16 +0000 [warn]: both of Plugin @id and path for <storage> are not specified. Using on-memory store.
2020-10-15 08:21:16 +0000 [info]: using configuration file: <ROOT>
  <source>
    @type forward
    @id input1
    @label @mainstream
    port 24224
  </source>
  <source>
    @type sample
    @label @mainstream
    rate 1
    tag "raw.sample"
  </source>
  <label @mainstream>
    <filter **>
      @type stdout
    </filter>
    <match **>
      @type kinesis_firehose
      @id out_kinesis_firehose
      region "ap-northeast-1"
      delivery_stream_name "fluentd-streams"
      <web_identity_credentials>
        role_arn "arn:aws:iam::123456789012:role/eksctl-kinesis-eks-addon-iamserviceaccount-d-Role1-SUPERPOWER"
        role_session_name "test-kinesis-session-name"
        web_identity_token_file "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
      </web_identity_credentials>
      <buffer>
        flush_interval 10s
        chunk_limit_size 1m
        flush_thread_burst_interval 1
        flush_thread_count 2
      </buffer>
    </match>
  </label>
</ROOT>
2020-10-15 08:21:16 +0000 [info]: starting fluentd-1.11.4 pid=6 ruby="2.6.6"
2020-10-15 08:21:16 +0000 [info]: spawn command to main:  cmdline=["/usr/local/bin/ruby", "-Eascii-8bit:ascii-8bit", "/fluentd/vendor/bundle/ruby/2.6.0/bin/fluentd", "-c", "/fluentd/etc/fluent.conf", "-p", "/fluentd/plugins", "--gemfile", "/fluentd/Gemfile", "--under-supervisor"]
2020-10-15 08:21:17 +0000 [info]: adding filter in @mainstream pattern="**" type="stdout"
2020-10-15 08:21:17 +0000 [info]: adding match in @mainstream pattern="**" type="kinesis_firehose"
2020-10-15 08:21:17 +0000 [info]: adding source type="forward"
2020-10-15 08:21:17 +0000 [info]: adding source type="sample"
2020-10-15 08:21:17 +0000 [warn]: #0 both of Plugin @id and path for <storage> are not specified. Using on-memory store.
2020-10-15 08:21:17 +0000 [warn]: #0 both of Plugin @id and path for <storage> are not specified. Using on-memory store.
2020-10-15 08:21:17 +0000 [info]: #0 starting fluentd worker pid=11 ppid=6 worker=0
2020-10-15 08:21:17 +0000 [info]: #0 [input1] listening port port=24224 bind="0.0.0.0"
2020-10-15 08:21:17 +0000 [info]: #0 fluentd worker is now running worker=0
2020-10-15 08:21:18.081312494 +0000 raw.sample: {"message":"sample"}
2020-10-15 08:21:19.082577481 +0000 raw.sample: {"message":"sample"}
2020-10-15 08:21:20.083657521 +0000 raw.sample: {"message":"sample"}
2020-10-15 08:21:21.084679969 +0000 raw.sample: {"message":"sample"}
# ...

しばらくすると、S3にfirehoseに入れられたログが入れられます。 aws s3 lsコマンドを実行すると、バケットが作成されていることが分かります。

$ aws s3 ls
# ...
2020-10-15 11:52:43 fluentd-eks-firehose

ここまでで、下記のAWS Kinesis FirehoseによるS3へのログの送信の動作が確認できました。

Fluentd Kinesis plugin Pod ---> AWS Kinesis Data Firehose ---> S3 bucket

EKSクラスタを削除する

動作確認が終了したので、EKSクラスタを削除します。

$ eksctl delete cluster --name kinesis-eks
[ℹ]  eksctl version 0.29.1
[ℹ]  using region ap-northeast-1
[ℹ]  deleting EKS cluster "kinesis-eks"
[ℹ]  deleted 0 Fargate profile(s)
[✔]  kubeconfig has been updated
[ℹ]  cleaning up AWS load balancers created by Kubernetes objects of Kind Service or Ingress
[ℹ]  3 sequential tasks: { delete nodegroup "ng-cb168d52", 2 sequential sub-tasks: { 2 sequential sub-tasks: { delete IAM role for serviceaccount "default/kinesis-eks-serviceaccount", delete serviceaccount "default/kinesis-eks-serviceaccount" }, delete IAM OIDC provider }, delete cluster control plane "kinesis-eks" [async] }
[ℹ]  will delete stack "eksctl-kinesis-eks-nodegroup-ng-cb168d52"
[ℹ]  waiting for stack "eksctl-kinesis-eks-nodegroup-ng-cb168d52" to get deleted
[ℹ]  will delete stack "eksctl-kinesis-eks-addon-iamserviceaccount-default-kinesis-eks-serviceaccount"
[ℹ]  waiting for stack "eksctl-kinesis-eks-addon-iamserviceaccount-default-kinesis-eks-serviceaccount" to get deleted
[ℹ]  deleted serviceaccount "default/kinesis-eks-serviceaccount"
[ℹ]  will delete stack "eksctl-kinesis-eks-cluster"
[✔]  all cluster resources were deleted

kinesis-eksという名前のEKSクラスタは削除されました。

開発元にIRSA対応のパッチをフィードバック

作成したIRSA対応のパッチにについてはkinesisプラグインの開発元にフィードバック済みです。 https://github.com/awslabs/aws-fluent-plugin-kinesis/pull/208 Issueチケットも前後してしまいましたが、切っています。 https://github.com/awslabs/aws-fluent-plugin-kinesis/issues/209

この記事では解説していませんがIRSA対応を行うにあたって、テストコードの追加も行っていますので興味のある方は上記URLから確認してみてください。

まとめ

fluent-plugin-kinesisのIRSA対応を実施した話を説明してみました。 AWSでは全てのリソースへリソースネーム(ARN)が振られています。 このリソースネームはEKSクラスタ上に作成したサービスアカウントにも振られています。 IRSAの仕組みに載っかってPodにサービスアカウントを紐づけると、動作しているPodにはAWS_ROLE_ARN環境変数とAWS_WEB_IDENTITY_TOKEN_FILE環境変数が紐づきます。 これらにより、それぞれサービスアカウントに紐づいたリソースネーム(ARN)と認証情報が記載されたファイルパスが参照可能になります。 この二つの環境変数はサービスアカウントを紐付けたPodであれば自動的に付与される情報のため、 EKSクラスターを使用するユーザーは定義ファイルにてこれら二つの環境変数を参照すると、適切な認証情報をPod内のプログラムに与えることができます。

当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。

  1. 公式の詳細な解説記事はこちらです: https://aws.amazon.com/jp/blogs/news/introducing-fine-grained-iam-roles-service-accounts/

  2. 当然のことながら、プロダクションで使う際にはFullAccessの権限ではなく、より細分化したIAM Roleのarnを指定するべきです