クリアコードでは、Fluentdの開発に参加しています。Fluentdにはtd-agentと呼ばれるディストリビューションがあり、各種プラグインをまとめてパッケージやインストーラーが提供されています。
今回は、td-agentをWindowsでインストールする際、バッチ実行時にコマンドプロンプトの表示を抑制するのに使った方法について紹介します。
なおWiXのバージョンはこの記事を書いている時点の最新版である3.11.2を前提としています。
td-agentでは、Windowsでインストールするプロセスの最後に、後始末やFluentdをサービスとして登録する目的でいくつかバッチファイルを実行しています。
その際、コマンドプロンプトが表示されてしまうので、ちょっとインストーラーの出来としてはいまいちでした。
WiXにはQuiet Execution Custom Actionというカスタムアクションを定義するためのものがあります。
それを使って、バッチ実行時のコマンドプロンプト表示を抑制するようにしたのが次の.wxsです。
<?xml version='1.0'?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
...(途中省略)..
<!-- td-agent-post-install.batに関するカスタムアクションを指定する -->
<Property Id="PostInstall" Value=" "/>
<CustomAction Id="SetPostInstallCommand"
Property="PostInstall"
Value=""[PROJECTLOCATION]bin\td-agent-post-install.bat""/>
<CustomAction Id="PostInstall"
BinaryKey="WixCA"
DllEntry="WixQuietExec64"
Execute="deferred"
Return="check"
Impersonate="no" />
<!-- td-agent.batに関するカスタムアクションを指定する -->
<Property Id="InstallFluentdWinSvc" Value=" "/>
<CustomAction Id="SetInstallFluentdWinSvcCommand"
Property="InstallFluentdWinSvc"
Value=""[PROJECTLOCATION]bin\td-agent.bat" --reg-winsvc i --reg-winsvc-delay-start --reg-winsvc-auto-start --reg-winsvc-fluentdop
t "-c [PROJECTLOCATION]etc\td-agent\td-agent.conf -o [PROJECTLOCATION]td-agent.log""/>
<CustomAction Id="InstallFluentdWinSvc"
BinaryKey="WixCA"
DllEntry="WixQuietExec64"
Execute="deferred"
Return="check"
Impersonate="no" />
<!-- 以下でインストール時の実行順を指定している -->
<InstallExecuteSequence>
<Custom Action="SetPostInstallCommand" After="InstallFiles">NOT Installed</Custom>
<Custom Action="PostInstall" After="SetPostInstallCommand">NOT Installed</Custom>
<Custom Action="SetInstallFluentdWinSvcCommand" After="InstallFiles">NOT Installed</Custom>
<Custom Action="InstallFluentdWinSvc" After="SetInstallFluentdWinSvcCommand">NOT Installed</Custom>
</InstallExecuteSequence>
</Product>
</Wix>
上記サンプルは、3つの部分から構成されています。
<!-- td-agent-post-install.batに関するカスタムアクションを指定する -->
<Property Id="PostInstall" Value=" "/>
<CustomAction Id="SetPostInstallCommand"
Property="PostInstall"
Value=""[PROJECTLOCATION]bin\td-agent-post-install.bat""/>
<CustomAction Id="PostInstall"
BinaryKey="WixCA"
DllEntry="WixQuietExec64"
Execute="deferred"
Return="check"
Impersonate="no" />
上記はプロパティ PostInstall
の値を設定するカスタムアクション SetPostInstallCommand
と WinQuietExec64
を使ってコマンドプロンプトを表示せずに実行するためのPostInstall
カスタムアクションから構成されています。
プロパティ PostInstall
の初期値が Value=" "
となっているのは意図したものです。このようにしないとエラーになります。
また、実行するコマンドを指定するときには、 "
で囲ってあげないといけません。
WixQuietExec64
で実行するコマンドは、カスタムアクションのId
と同名のProperty
で指定するというのがポイントです。
なお、WixQuietExec64
を使えるようにするために、 light.exe
実行時に -ext WixUtilExtension
オプションを明示的に指定して実行する必要があることに注意してください。
td-agent-post-install.bat
に関するカスタムアクションを指定する部分とほぼ同様なので説明を省略します。
<!-- 以下でインストール時の実行順を指定している -->
<InstallExecuteSequence>
<Custom Action="SetPostInstallCommand" After="InstallFiles">NOT Installed</Custom>
<Custom Action="PostInstall" After="SetPostInstallCommand">NOT Installed</Custom>
<Custom Action="SetInstallFluentdWinSvcCommand" After="InstallFiles">NOT Installed</Custom>
<Custom Action="InstallFluentdWinSvc" After="SetInstallFluentdWinSvcCommand">NOT Installed</Custom>
</InstallExecuteSequence>
上記は、インストール時に実行されるカスタムアクションの実行順序を指定しています。上から順に実行されます。
それぞれのカスタムアクションの意味は次のとおりです。
After="InstallFiles"
で指定し、カスタムアクションSetPostInstallCommand
を実行するSetPostInstallCommand
の後にPostInstall
を実行するというのをAfter="SetPostInstallCommand"
で指定するAfter="InstallFiles"
で指定し、カスタムアクションSetFluentdWinSvcCommand
を実行するSetFluentdWinSvcCommand
の後にInstallFluentdWinSvc
を実行するというのをAfter="SetFluentdWinSvcCommand"
で指定するすでに説明した、プロパティの設定と、コマンドを実行するためのカスタムアクションのペアであることがわかります。
今回は、WiX ToolsetでQuiet Execution Custom Actionを使ってバッチ実行時のコマンドプロンプト表示を抑制する方法を紹介しました。
td-agent 4.0.1のリリースには間にあわなかったのですが、次の4.x系のリリースに含まれるはずです。
当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。
クリアコードはFluentdの開発に参加しています。
Fluentd本体といくつかのプラグインがセットになったtd-agentというパッケージが提供されていますが、
td-agentのversion 4からaarch64(arm64)やppc64le(PowerPC)といった非x86_64環境にもパッケージの提供を開始しました。
そのためtd-agent4のppc64leパッケージの動作確認をする必要がありましたが、実ハードが無いのでx86_64上のqemu-system-ppc64leで動作確認を行いました。
QEMUとは、FLOSSのプロセッサシミュレーターです。
今回はその手順を畑ケが紹介します。 *1
libvirtにてppc64leのCentOS 8環境を立ち上げるには、QEMUのディスクイメージを作成する必要があります。
libvirtを使うホスト環境はUbuntu 18.04.5 LTS amd64環境です。
今回の動作確認に必要なコマンドをインストールします。
$ sudo apt install libvirt-clients virtinst libvirt-daemon-system qemu-utils qemu-system-ppc
CentOS 8 ppc64leのDVDイメージを取得します。
http://isoredirect.centos.org/centos/8.2.2004/isos/ppc64le/ より、近いミラーからCentOS 8のイメージを取得します。
ネットワークインターフェースの設定を行えばDVDイメージでなくてもインストールできますが、
今回はDVDイメージをダウンロードしてきました。
CentOS 8の最新版のDVD ISOイメージのCentOS-8.2.2004-ppc64le-dvd1.isoを入手しました。
CentOS 8をインストールする準備が整ったので、ppc64leのCentOS 8のDVDからブートしてみます。*2
$ sudo virt-install --name=centos8-ppc64le --ram=4096 --vcpus=4 \
--os-type=linux --os-variant=rhel7 --arch=ppc64le \
--machine pseries-2.12 \
--disk=/var/lib/libvirt/images/centos8-ppc64le.img,format=qcow2,size=20 \
--cdrom=CentOS-8.2.2004-ppc64le-dvd1.iso \
--serial=pty --console=pty --boot=hd --graphics=none
ネットワークにはlibvirtが作成しているデフォルトネットワーク(virbr0)を指定します。
CentOSのインストーラが起動するので、指示に従ってインストールします。
後続の節ではCentOS 8 ppc64le環境が出来たものとして説明します。
virt-installでネットワークの設定を有効化してインストールした場合、ゲストのip a
の出力は以下のようになります。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:2b:5e:d1 brd ff:ff:ff:ff:ff:ff
inet 192.168.123.250/24 brd 192.168.123.255 scope global dynamic noprefixroute enp0s1
valid_lft 3462sec preferred_lft 3462sec
inet6 fe80::2e8c:ddc7:91ff:f5fe/64 scope link noprefixroute
valid_lft forever preferred_lft forever
ゲストからインターネットにも出られます。
$ ping -c4 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=9.95 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=58 time=8.37 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=58 time=7.60 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=58 time=7.72 ms
--- 1.1.1.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 9ms
rtt min/avg/max/mdev = 7.599/8.410/9.952/0.939 ms
libvirtで立ち上げているゲストへホスト環境からscpでファイルを送ります。
今回作成した仮想マシンには192.168.123.250が割り当てられています。
$ scp /path/to/td-agent-4.0.1-1.el8.ppc64le.rpm <awesome_user>@192.168.123.250:
libvirtで動作しているゲストのCentOS 8 ppc64le環境にてdnfを使ってscpを使って転送してきたパッケージをインストールします。
$ sudo dnf install -y td-agent-4.0.1-1.el8.ppc64le.rpm
[sudo] password for user:
Failed to set locale, defaulting to C.UTF-8
CentOS-8 - AppStream 2.0 MB/s | 5.0 MB 00:02
CentOS-8 - Base 1.9 MB/s | 1.9 MB 00:01
CentOS-8 - Extras 9.4 kB/s | 8.1 kB 00:00
Last metadata expiration check: 0:00:01 ago on Mon Oct 19 22:28:38 2020.
Dependencies resolved.
======================================================================================================================
Package Architecture Version Repository Size
======================================================================================================================
Installing:
td-agent ppc64le 4.0.1-1.el8 @commandline 29 M
Transaction Summary
======================================================================================================================
Install 1 Package
Total size: 29 M
Installed size: 96 M
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Running scriptlet: td-agent-4.0.1-1.el8.ppc64le 1/1
Installing : td-agent-4.0.1-1.el8.ppc64le 1/1
Running scriptlet: td-agent-4.0.1-1.el8.ppc64le 1/1
Created symlink /etc/systemd/system/multi-user.target.wants/td-agent.service \u2192 /usr/lib/systemd/system/td-agent.service.
prelink detected. Installing /etc/prelink.conf.d/td-agent-ruby.conf ...
Verifying : td-agent-4.0.1-1.el8.ppc64le 1/1
Installed:
td-agent-4.0.1-1.el8.ppc64le
Complete!
libvirtゲスト環境にてtd-agentサービスを上げてみます。
$ sudo systemctl start td-agent
libvirtゲスト環境にて立ち上がっていることを確認します。
$ systemctl status td-agent
● td-agent.service - td-agent: Fluentd based data collector for Treasure Data
Loaded: loaded (/usr/lib/systemd/system/td-agent.service; enabled; vendor pr>
Active: inactive (dead)
Docs: https://docs.treasuredata.com/articles/td-agent
$ sudo systemctl start td-agent
$ sudo systemctl status td-agent
● td-agent.service - td-agent: Fluentd based data collector for Treasure Data
Loaded: loaded (/usr/lib/systemd/system/td-agent.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2020-10-19 22:32:44 EDT; 17s ago
Docs: https://docs.treasuredata.com/articles/td-agent
Process: 9127 ExecStart=/opt/td-agent/bin/fluentd --log $TD_AGENT_LOG_FILE --daemon /var/run/td-agent/td-agent.pid >
Main PID: 9236 (fluentd)
Tasks: 4 (limit: 22015)
Memory: 134.6M
CGroup: /system.slice/td-agent.service
├─9236 /opt/td-agent/bin/ruby /opt/td-agent/bin/fluentd --log /var/log/td-agent/td-agent.log --daemon /var>
└─9239 /opt/td-agent/bin/ruby -Eascii-8bit:ascii-8bit /opt/td-agent/bin/fluentd --log /var/log/td-agent/td>
Oct 19 22:32:12 localhost.localdomain systemd[1]: Starting td-agent: Fluentd based data collector for Treasure Data...
Oct 19 22:32:44 localhost.localdomain systemd[1]: Started td-agent: Fluentd based data collector for Treasure Data.
無事に立ち上がっている事を確認しました。
td-agent 4のppc64leのパッケージを題材にppc64leのQEMU環境を作成し、実際にパッケージをインストールできるか、また、サービスとして立ち上げられるかを確認した時のやり方を思い出しながらまとめました。
FLOSSを触っていると大抵の場合x86_64環境や、行ってもaarch64(arm64)環境がせいぜいのため、このような大掛かりなエミュレーション環境を準備する事は少ないかもしれません。
しかし、Fluentdのような大きなプロジェクトになるとPowerPC環境でのパッケージを必要とされることがあります。その場合にも慌てないでエミュレーション環境を作成して動作確認を実施してみてはいかがでしょうか。
当社では、お客さまからの技術的なご質問・ご依頼に有償にて対応するFluentdサポートサービスを提供しています。Fluentd/Fluent Bitをエンタープライズ環境において導入/運用されるSIer様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。
クリアコードは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を使うことで非rootユーザーを作成し、必要な権限を必要なだけユーザーに付与できます。
S3を参照できるだけのIAMを作成しある非ルートユーザーに紐付けると、そのユーザーはS3のバケットのコンテンツしか参照することができない、という状態を作ることができます。
IAM Roles for Service Accounts (IRSA)という機能はIAMの認証情報をEKS上のPod毎に割り当てられるようにする仕組みです。 *1
k8s上でIAMを割り当てる仕組みはkube2iam、kiamなどがあります。
kube2iamに関してはワーカーにIAMを割り当てます。
kiamはノード全体に対してIAMを割り当てます。
これらのFLOSSのソリューションに対して、
IRSAではPodに対してIAMを割り当てられるようになっています。
つまり、Pod毎に最小のIAMロールを与えることが可能になります。
IRSAはAWSのEKSクラスターのPod内の認証方法なので、EKSクラスターを立ち上げます。
Amazon EKSクラスターの操作にはawsコマンドのインストールとkubernetesの操作を行うためのkubectlのインストールとAWS EKSのクラスターの操作のためのeksctlのインストールがそれぞれ必要です。
aws cli
、kubectl
、eksctl
がそれぞれインストールされたものとして認証情報を入れ込んでいきます。
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
ここでは東京リージョンをデフォルトリージョンとして設定しています。
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
数十分待つと、デプロイが完了しました。
$ 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対応が入っていません。
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>
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"]
設定ファイルなどは以下。
Dockerコンテナのエントリポイントのスクリプトを作成します。
#!/usr/bin/env sh
exec fluentd -c /fluentd/etc/${FLUENTD_CONF} -p /fluentd/plugins --gemfile /fluentd/Gemfile ${FLUENTD_OPT}
@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>
<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
AWS Kinesis Data Firehoseはストリーム名をfluentd-streams
、配信先をS3のfluentd-eks-firehose
バケットに、送信方法はDirect PUT and other sourcesを指定してあります。
今回の記事はIRSA対応を主に解説したい記事のため、設定方法の解説は省略します。
k8sではConfigMapにより、Podで使用する設定ファイルなどの内容を差し替える機能があります。
作成したテスト用Dockerfileでも動作確認を行うことは可能ですが、in_sample
からのサンプルイベントを流した時に標準出力でイベントの内容を確認できない状態になっているため、その辺りを修正したConfigMapを定義します。
また、fluent-plugin-kinesisが動作するEKSのPodにfirehoseのIRSAの認証情報をアタッチしたいため、このサービスアカウントの情報もPodの定義ファイルに記入します。
---
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
しばらく経つと、STATUS
がRunning
に変わります。
$ 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クラスタを削除します。
$ 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対応のパッチにについては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様、サービス提供事業者様は、お問い合わせフォームよりお問い合わせください。
結城です。
皆さんは「Hacktoberfest」をご存じでしょうか。OSSにコントリビュートする人を増やしたり、OSSへのコントリビューション自体を増やしたりすることを意図して、「どんなOSSプロジェクトでも、どんな内容でも構わないので、GitHub上でプルリクエストを4件作成したらイベントロゴ入りのTシャツが貰える」というルールで毎年開催されているイベントです。
今年の10月初頭、このイベントがきっかけで大量の迷惑なプルリクエスト(spam PR)が発生するという事件がありました*1。この件を承けて書かれた記事の1つとして、OSSプロジェクトへの望ましいコントリビューションの仕方について語った 6 Things to Avoid When Contributing to Open-Source Projects - Qvault という記事があります。こちらの記事で語られている「コントリビュートするときに避けるべき6つのこと(するべき6つのこと)」は、要約すると以下の通りです。
squash
で1つのコミットにまとめる)それぞれは妥当な話なのですが、筆者はこの記事について、期待されているような効果はあまりなく、逆に予期しない萎縮効果を生んでしまうのではないか、と懸念しています。
この記事を書いた人は恐らく、spam PRをしてしまう人達に記事を読んでもらいたかったのでしょう。しかし、実際にspam PRをしていた人達にとっては、1から5のことに気をつける動機がありません*2。それに対し、「まだコントリビュートをした経験はないが、OSSにコントリビュートしたいと思っている、向上心のある人」にとっては、この6箇条はむしろ、最初の一歩を踏み出しにくくさせるブレーキとなりえます。その結果としてOSSへの実際のコントリビュートが減ってしまっては、本末転倒です。
ということで、これから何回かのシリーズに分けて、先の記事の6箇条について、可能な限り具体的に・理由を示しながら「こういう考え方はよくない」「こう考えれば問題ない」と説明してみます。先の記事のような「べからず集」を見て萎縮してしまった心を解きほぐす一助となれば幸いです。
まず1つ目は、複数のトピックを含んだプルリクエストについてです。
公開のOSSプロジェクトで問題の修正に取り組み始めてみると、「なんだかすごい物」と思っていたOSSのコードが、実は普通のコード……どころか、意外と粗だらけだということに気が付くことがあるかもしれません。
そう感じるのは、不思議なことではありません。というのも、有志の手により開発されているOSSは、「ものすごく技術力の高い人が作っている、完成度の高いソフトウェア」であるとは限らず、「切実にそれを必要としていた人がDIYの精神で作っている、間に合わせのソフトウェア」である場合も多々あるからです。もしかしたら、あなたの方がプロジェクトオーナーより技術力が高い場合すらあるかもしれません。
そういうときに、「この書き方は効率が悪いから駄目だと教わったやつだ」とか、「こういう設計は分かりにくいから避けろと言われたやつだ」といった要領で、本来やりたかったことと無関係ながらついでに直したい箇所が目につくこともあるでしょう。あるいは、「これを応用すればこういう便利な機能も追加できるな」と気が付いて、そっちにも手を出したくなるかもしれません。
ですが、当初の修正と無関係な変更まで「ついで」でやってしまうのは、プルリクエストでは避けた方がよいです。開発者側で変更内容を把握しやすくするために、プルリクエストは1回につき1つの趣旨に則った変更のみに留めましょう。
具体的にどういうものがそれにあたるのかを説明します。たとえば、以下のようなJavaScriptのコードがあったとします。
// リンク先のURL文字列を収集して返す
function getLinkURLs() {
let links = document.querySelectorAll('a[href]');
// ↓プロパティ名を間違えている(hre ではなく href が正しい)
let urls = Array.from(links, link => link.hre);
return urls;
}
// ページ内にある見出し(h1~h6)を収集して返す
function getHeadings() {
// ↓h2とh3の間に「,」が抜けている('h1,h2,h3,h4,h5,h6' が正しい)
return document.querySelectorAll('h1,h2h3,h4,h5,h6');
}
見ての通り2箇所に間違いがありますが、こういう場面で「自分にも直せそう!」と思って両方を一度に変更してプルリクエストしてしまうのは良くないです。というのも、この2箇所の間違いは、表面的にはどちらも「誤記」ですが、それぞれ趣旨・性質が異なるからです。問題として報告するなら、この2つは
と、それぞれ別個に報告するのが適切です。
もし1つ目の問題の修正のためのプルリクエストに2つ目の問題の修正が混入していたら、開発者は「えっ、この変更はリンク先のURLと関係なさそうなんだけど……自分の知らない所で、これがリンク関係に影響してたの?」と混乱して、余計な調査に時間を使わないといけなくなります*3
また、後々後退バグが見つかったときの原因調査でも、このように複数の趣旨が混ざった変更があると、調査の妨げになりがちです。複数の趣旨が混ざったコミットやプルリクエストは、プルリクエストの時点でも問題を生むし、その後も問題を生むので、原則としては百害あって一利なしと言えるでしょう。
「ある変更をするために、それが依存することになる別の変更もしたい」というケースもよくあります。たとえば以下の要領です。
このようなケースでも、まず最初に「依存する変更」を単独のプルリクエストで行うのがおすすめです。本来やりたかったこと(Touch Bar対応やWindows 10の新機能との連携)が実現されないとしても、前提となる変更自体で得られるものがある場合、それらは本質的には「趣旨の異なる変更」と見なせるからです。
元々直したかった問題を直す過程で、その変更の範囲に自然に含まれる変更であれば、一度に行っても問題ないと言える場合はあります。たとえば、以下のようなコードがあったとしましょう。
function getLinkURLs() {
let links = document.querySelectorAll('a[href]');
// ↓プロパティ名を間違えている(hre ではなく href が正しい)
// ↓この行だけインデントが揃っていない
let urls = Array.from(links, link => link.hre);
return urls;
}
この場合、「プロパティ名の間違いを直す」趣旨の変更の一環でインデントも揃えてしまう、ということはよく行われます。動作を変えるために変更しないといけなかった行については、動作を変えない範囲の変更であればついでに行っていい、というのが一般的な慣習だと言っていいでしょう。
ただし、単に「同じ行なら複数の変更をまとめてやってしまってもよい」わけではない、ということには注意してください。1行の中であっても、趣旨の異なる変更は別々のプルリクエストに分けることが望ましいです。
細かいレベルでは別々の変更と言えるけれども、大局的には1つの趣旨にまとめられる、という場合には、1つのプルリクエストにしてよい場合もあります。たとえば、「Firefox 60までにしか対応していなかったFirefox用アドオンを、Firefox 68に対応させるため」という名目で以下の2つの変更を1つのプルリクエストにまとめるのは、問題無いと判断してもらえるかもしれません。
筆者の個人的な感覚では、この2つをまとめてプルリクエストすることは問題無いと感じます。しかし、プロジェクトオーナーによっては異なる判断をするかもしれません。心配な場合は、プルリクエストのコメントの中で「変更ごとに分けた方がよいでしょうか?(Should I separate this for each change?)」と尋ねてみるか、最初から分けてプルリクエストすることをおすすめします。
以上、「複数の事を1つのプルリクエストにしない(プルリクエストはトピックごとに分ける)」という原則について、実例を挙げて解説してみました。
この解説は、OSSへのコントリビューションを増やすことを意図した取り組みであるOSS Gateで開催しているワークショップの中で得られた知見をまとめた本、「これでできる! はじめてのOSSフィードバックガイド」の一部を抜粋・再編集した物です。本編ではこのほかにも、問題の報告の仕方やありがちなミス、フィードバック初心者の方が戸惑いがちな点について、なるべく具体例を示しながら、幅広く解説してみています。リンク先では原稿の全文を公開していますが、手元に置いて参照しやすい形式での販売も行っていますので、読書スタイルに合った形式で参照して頂ければ幸いです。
OSS Gateでは、新型コロナウィルスの感染拡大防止の観点から、現在は東京地域を主体としたワークショップをオンライン(Discord)で開催しています。次回開催予定は10月31日(土曜)10:30からで、ビギナー(ワークショップで初めてのフィードバックを体験してみたい人)・サポーター(ビギナーにアドバイスする人)のどちらも参加者を募集中です。ご都合の付く方はぜひエントリーしてみて下さい。
また、当社では企業内での研修としてのOSS Gateワークショップの開催も承っています(例:アカツキさまでの事例)。会社としてOSSへの関わりを増やしていきたいとお考えの企業のご担当者さまは、お問い合わせフォームからご連絡頂けましたら幸いです。
*1 事件の概要はGIGAZINEの記事にまとめられています。記事によると、前年までも迷惑なプルリクエストあったようですが、今年はそれと比べものにならないレベルで迷惑行為が多発した模様です。そのためHacktoberfestは運用ルールが変更され、現在は「イベントへの協賛を明示的に示したOSSプロジェクトに対してプルリクエストを行い、そのプルリクエストに承認のラベルが付けられた」状態のみカウントされるようになっています。
*2 なぜなら、彼らの動機は「Tシャツを貰うこと」にあり、それを達成するのに最も簡単な方法としてspam PRを選んだだけに過ぎません。1から5のことを守らないとTシャツを貰えないのなら、コストパフォーマンスが悪いので去るだけでしょう。
*3 開発者は今あるコードのすべてを把握できているとは限りません。変更に変更を重ねた結果、自分の意識していないところでの変更が別の所に影響するようになってしまっている、ということはよくあります。そのため、常に変更の副作用を疑う癖が付いているのです。
第13回目のノータブルコードでは、collectdをビルドするときに印象に残ったconfigure.ac
のコードを紹介します。
collectd
はサーバーの情報を収集するのに便利なフリーソフトウェアです。
プラグインを有効にすることで様々なシステム情報を収集できるようになっています。
プラグインをPerl、Python、Luaといったスクリプト言語で記述することもできます。
印象に残ったconfigure.ac
のコードは、Lua
を有効にしてビルドしようとしたときに気づいたものです。
collectd
では、Lua
の各種バージョンを自動検出するようにしています。
具体的には以下のバージョンを検出するようにしています。
実際にライブラリーを検出するのには、PKG_CHECK_MODULES
を使っています。
どれくらい頑張って検出しようとしているかというと、これぐらい頑張っています。
PKG_CHECK_MODULES([LUA], [lua],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua-5.3],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua5.3],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua53],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua-5.2],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua5.2],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua52],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua-5.1],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua5.1],
[with_liblua="yes"],
[
PKG_CHECK_MODULES([LUA], [lua51],
[with_liblua="yes"],
[with_liblua="no (pkg-config cannot find liblua)"]
)
]
)
]
)
]
)
]
)
]
)
]
)
]
)
]
)
]
)
Lua
には、JITコンパイラであるLuaJIT
という実装もあります。
上記のコード片では、LuaJIT
は自動的に検出できません。
しかし、次のように LIBLUA_PKG_CONFIG_NAME
を指定することでLuaJIT
を有効にできるようになっています。
# --with-liblua {{{
AC_ARG_VAR([LIBLUA_PKG_CONFIG_NAME], [Name of liblua used by pkg-config])
if test "x$LIBLUA_PKG_CONFIG_NAME" != "x"
then
PKG_CHECK_MODULES([LUA], [$LIBLUA_PKG_CONFIG_NAME],
[with_liblua="yes"],
[with_liblua="no"]
)
else
LuaJIT
の場合、.pc
ファイルが luajit.pc
なので、LIBLUA_PKG_CONFIG_NAME=luajit
を指定してconfigure
を実行すればよいです。
$ ./configure LIBLUA_PKG_CONFIG_NAME=luajit
このようにして、configure.ac
で明示的に自動検出するようになっていなくても、pkg-config
をサポートしている実装であれば有効にできるように配慮されています。
以上、複数の処理系やバージョンを考慮してライブラリーを検出できるようにしているコードの紹介でした。
企業の運用では複数のブラウザを使い分けることがままあります。例えばこんな具合です。
「どうして一つのブラウザで両方見ないのか?」という疑問を覚えるかもしれませんが、このような構成を採用する理由には、大きく分けて以下の二種類があります。
レガシーブラウザを使う必要がある場合。 典型的には、社内サイトが古いIEにしか対応していない場合がこれに該当します。インターネットでは、既にIEのサポートを終了していて、IEでは正常に表示できなくなっているアプリやサイトが少なくありません。このため、社内サイトの閲覧用に限って、IEを温存させる必要があるというのは、現実の運用では非常によくあるパターンです。
セキュリティ的に分けたい場合。 これは用途に応じてブラウザを分けてしまおうというアイデアです。従来から「Web分離」という文脈で論じられてきた対策の一つのバリエーションで、業務用とインターネット閲覧でブラウザを分けることで、堅牢性を高めようというセキュリティ戦略です。
クリアコードでは、こういったユースケースをサポートするためにBrowserSelectorというツールを開発しています。
簡単に言えば 「URLごとにブラウザを自動的に切り替えるツール」 です。
冒頭に掲げた図のケースであれば、次のように設定ファイルを置くだけで、自動的にブラウザを切り替えられるようになります。
[Common]
DefaultBrowser=firefox
CloseEmptyTab=1
[URLPatterns]
0001=http*://intra.example.com/*|ie
この基本機能に加えて、次のような機能も備えています。
(機能1)設定の集中管理に対応
(機能2)モダンブラウザサポート
(機能3)外部アプリとの連携機能
昨年、最初のバージョンを公開してから、導入企業様のフィードバックを集めつつ、着実に機能が増えていっています。
動作イメージを掴んでもらうために動画を用意しました。
(1) BrowserSelectorのリリースページにアクセスしてください。
(2) ページのリンクからMSIインストーラをダウンロードして実行します。
(3) 設定ファイルを「C:\Program Files(x86)\ClearCode\BrowserSelector」に配置します。
基本的にこれで利用できるようになります。設定可能な項目の一覧などの詳しい使い方は BrowserSelector利用ガイド (docx/72kb)を配布しているので、そちらを参照してください。
BrowserSelectorはMITライセンスで配布されている自由なソフトウェアです。
サポートが必要な場合は、お問い合わせフォームからご連絡ください。
結城です。
OSSプロジェクトへのコントリビュートの「べからず集」記事について、経験の浅い方が「自分のしようとしていることもそれにあてはまるのではないか?」と心配になってコントリビュートをためらうことがないように、具体的な例と考え方を紹介するシリーズの2本目です。今回は、2つ目の「べからず」として挙げられている、コードの一貫性について説明します。
プロジェクトオーナーが困惑するケース、変更をマージしてもらえないケースでよくあるのが、コードであったり設計方針だったり運営方針だったりと行ったさまざまなレベルで、そのプロジェクトに「馴染まない」「一貫性を壊す」変更になってしまっているケースです。
元のコードに馴染むコードを書けるかどうかは技術力の有無とはあまり関係が無く、「そもそも馴染ませようという気があるか、ないか」という意識の差異が大きいです。別の角度から言うと、コードの書き方、いわゆるコーディングスタイルが統一されていることの意義をどう捉えているか、ということです。
日本語の文章では、全く同じ内容の文章でも、どこに句読点を入れるか・漢字で書くかひらがなで書くか・「プリンター」と書くか「プリンタ」と書くか、といった「表記揺れ」があり得ます。プログラムにおいても、言語によってはそれと同様に、動作の内容自体は同じでも表記揺れが起こり得る部分があります。
たとえば、JavaScriptでは以下のような表記揺れがあります。
if(experssion){
と書くのか?if ( experssion )≪改行≫{
と書くのか?if (≪改行≫experssion≪改行≫)≪改行≫{
と書くのか?;
)を必ず書くのか、書かないのか?if
での条件分岐で実行する内容が1つだけのとき、波括弧を使うのかどうか?「そんなの、動けばどっちでもいいじゃないか」と思いますか? だとしたら、それはあなたが、コードの表記が統一されていないことが原因で発生するトラブルを、まだ体験したことがないからでしょう。
実際には、コードの中に表記揺れが多いと、誤読や誤記によって問題が発生する原因になります。また、コードを変更する人がその都度「こっちの書き方がいい」と思った表記に変えてしまうことが繰り返されると、行の変更理由を調べても意味ある情報を得られなくなってしまいます。コードの表記揺れは、百害あって一利なしです。プロジェクトのオーナーは、ただ意地悪や気分で「馴染まない変更」を拒絶するのではなく、プロジェクトにとって現実的なデメリットがあるから拒絶しているのです。
こういった問題に悩まされずに開発に集中できるようにするために、プロジェクト全体で一定のルールに則って書き方を統一し表記揺れをなくすことを、「コーディングスタイルを揃える」と言います。
「外部のコントリビューターがコードの書き方まで揃えるのは大変だから、プロジェクトオーナー側で変更のマージ後に直してくれればいいのに」と思う人もいるかもしれません。実際に、そのような運用を取っているプロジェクトもあります。
しかし、プルリクエストの数が多いと、プロジェクトオーナーはそのような本質的でない作業に忙殺される羽目になってしまいます。また、リポジトリのコミット履歴も本質的でない変更ばかりになってしまいます。「プロジェクトオーナーの手が回らないところを手助けしてプロジェクトに協力する」のがコントリビューションの本質なのに、そんなふうにプロジェクトの運営を妨げてしまっては本末転倒ですよね。
前述のように機械的な判別が容易な部分のコーディングスタイルは、昨今のOSSでは、ソフトウェアで自動的に揃える運用を取っていることが多いです。prettierやESLintはそのようなことをするソフトウェアの代表的な例ですし、VisualStudio Codeなどのテキストエディターを適切に設定していれば、ファイルの保存時に自動的にコーディングスタイルを揃えさせることもできます。
そのような運用を取っていないプロジェクトでは、コーディングスタイルを意識して合わせる必要があります。
歴史の長いプロジェクトでは、GNUのコーディング規約やMozillaのコーディング規約のように、文章の形でコーディング規約を明記している場合があります。
そういった規約が特に定められていない場合は、そのプロジェクトの他のコードを見て、変更を加えた箇所のコードの書き方が他の箇所と同じになるように揃えることになります。つまり、変更対象の既存のコードを、変更箇所以外も含めてもう少し読み込んで、傾向を把握する必要があります。
また、これは本題から外れますが、コーディングスタイルをソフトウェアで自動的に揃える運用をまだ取っていないプロジェクトでは、現在のコーディングスタイルを強制するルールをまとめた上で、自動化の仕組みを導入する提案をしてみてもよいでしょう(当然、これはその変更だけの単独のプルリクエストにすることを強くお勧めします)。
多くのOSSのコードでは、クラスや変数などの名前付けに英語の単語や熟語が使われています。同じことを言い表すのにも、考え方次第で色々な表現の仕方があり、どのような表現・表記を主に使うかはプロジェクトによって方針が異なります。特にポピュラーな表現のパターンを、以下にいくつか挙げてみましょう。
複数のデータを保持する変数の名前付けには、いくつかのパターンがあります。
似た例として、個数や件数などの*「数」を保持する変数やプロパティの名前付け*には、以下のようなパターンがあります。
関数名・メソッド名の動詞にもパターンがあります。たとえば、原形にするか現在形にするかは、何をさせる機能かという目的で変わることが多いです。
特定の文脈で使われやすい単語や表現にも、いくつかの流儀があります。状態を問い合わせる場面では、以下のような例があります。
データの取得や保存に関わる場面でも、以下のような例があります。
また、複数の単語からなる識別子の名前付け自体にも、以下のようなパターンがあり、それぞれよく使われる場面があります。
こういった点の名前付けの流儀を合わせることで、変更箇所のコードがより元のコードに「馴染む」ようになり、プロジェクトオーナーにとっても、よりマージしやすいプルリクエストになります。
プロジェクトによっては、こういった名前付けについてもコーディング規約で定めている場合があります。そのような明示的な規約がない場合は、やはり、変更対象の部分やその周囲の部分など、既存のコードを読んで傾向を把握する必要があります。
コードの変更そのものとは無関係ですが、コミットログの書き方の様式も、そのプロジェクトの流儀に合わせるようにしましょう。プロジェクトによってはコミットログをリリースノート作成時の参考資料にすることがあり、あまりに様式から外れていると、リリース時に開発者に余計な苦労を強いることになってしまいます。
コミットログは基本的には、5W1H*7のうちの「Why」にフォーカスをあてて書きます。たとえば以下のような要領です。
このような情報が含まれないコミットメッセージは、あまり喜ばれません。たとえば以下の要領です。
プロジェクトによっては、リポジトリに含まれるドキュメントの修正のコミットメッセージ冒頭には「doc:
」、自動テストの修正のコミットには「test:
」、といった印(プレフィクス)を付けるよう運用で定めている場合もあります。
コードの変更に取りかかるときは、その前に、開発への参加の仕方についての説明があればそれを参照して、運用ルール自体を把握するとよいでしょう。他の変更がどんなコミットメッセージでなされているか、実際のコミットログを確認するのもおすすめです。
以上、「一貫性を壊さない(コーディングスタイルを合わせる)」という原則について、実例を挙げて解説してみました。
この記事で紹介した内容以外にも、言語を問わず「読みやすさ」という観点にフォーカスしてコードの語彙を紹介している本として、「リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック(Theory in practice)」(オライリージャパンより、2012年刊行)があります。この本を読んでからOSSのコードに触れると、「あっ、この書き方は読みやすさのための工夫をしている!」という箇所に気付きやすくなり、プロジェクト内でどのような流儀を採用しているかをより読み取りやすくなるでしょう。
この解説は、OSSへのコントリビューションを増やすことを意図した取り組みであるOSS Gateで開催しているワークショップの中で得られた知見をまとめた本、「これでできる! はじめてのOSSフィードバックガイド」の一部を抜粋・再編集した物です。本編ではこのほかにも、問題の報告の仕方やありがちなミス、フィードバック初心者の方が戸惑いがちな点について、なるべく具体例を示しながら、幅広く解説してみています。リンク先では原稿の全文を公開していますが、手元に置いて参照しやすい形式での販売も行っていますので、読書スタイルに合った形式で参照して頂ければ幸いです。
OSS Gateでは、新型コロナウィルスの感染拡大防止の観点から、現在は東京地域を主体としたワークショップをオンライン(Discord)で開催しています。次回開催予定は10月31日(土曜)10:30からで、ビギナー(ワークショップで初めてのフィードバックを体験してみたい人)・サポーター(ビギナーにアドバイスする人)のどちらも参加者を募集中です。ご都合の付く方はぜひエントリーしてみて下さい。
また、当社では企業内での研修としてのOSS Gateワークショップの開催も承っています(例:アカツキさまでの事例)。会社としてOSSへの関わりを増やしていきたいとお考えの企業のご担当者さまは、お問い合わせフォームからご連絡頂けましたら幸いです。
*1 いわゆるタブ文字。
*2 いわゆる半角スペースを何個も書く方法。
*3 Pascalというプログラミング言語でよく見られた書き方であることから。
*4 ラクダのこぶのように見えることから。
*5 ヘビが地を這うイメージから。
*6 肉を串に刺して焼く料理「ケバブ」の様子に似ていることから。
*7 「Who(誰が)」「When(いつ)」「Where(どこで)」「What(何を)」「Why(なぜ)」「How(どのように)」という、ニュース記事を書くときに盛り込むべき情報の一覧を端的に示したもの。