- 投稿日:2020-07-03T23:16:10+09:00
【Docker】プロセスの分離とは
はじめに
Dockerを使用するに当たって、Dockerの特徴でもあるコンテナによるプロセスの分離について学んだのでまとめます。
仮想マシンvsコンテナ
仮想化ソフトウェアには「ホスト型仮想化ソフトウェア」や「ハイパーバイザー型仮想化ソフトウェア」、「コンテナ型仮想化ソフトウェア」などの種類があります。
※仮想化ソフトウェアの種類ハイパーバイザー型仮想化
ハイパーバイザー型の仮想化ソフトウェアはハイパーバイザーと呼ばれるソフトウェアが、仮想的なハードウェアである「仮想マシン」を提供し、それによってゲストOSからはあたかも物理的なマシンで稼働しているかのように見えています。
ハイパーバイザー型の仮想化ソフトウェアはハードウェアをエミュレーション(ある装置やソフトウェア、システムの挙動を別のソフトウェアによって模倣し代替として動作させること)しています。コンテナ環境
コンテナ環境は名前空間(namespace)とcgroupと呼ばれる資源管理の仕組みを使うことで単一のOS内で複数のコンテナがプロセス(Linux上で動いているプログラム)として稼働しています。
※Linuxの「プロセス」って何だろう?名前空間とは?
それでは、本題に入ります。
上記のハイパーバイザー型仮想化では、ハイパーバイザーにより仮想化を実現していましたが、Dockerによるコンテナ仮想化はどのようにして分離された空間を作り出しているのでしょうか?Dockerエンジンによるコンテナ仮想化を実現する上で重要なのが名前空間です。
広義での名前空間とは以下のようなものです。名前空間(なまえくうかん)はNamespaceの訳語で、名前の集合を分割することで衝突の可能性を低減しつつ参照を容易にする概念である。
名前空間とは何かを分離するものという概念を表しているようです。
Dockerにおける名前空間
単一のOS環境に置いて、Dockerは複数の分離された空間を作成できます。その分離された空間を実現しているのが名前空間(namespace)です。
名前空間を使うと、ファイルシステムやネットワーキング無堂、各コンテナのシステムリソースを分離できます。
例えば、ある分離された空間Aのプロセスは別の分離された空間Bには見えないといった制御をします。
また、名前空間はプロセスだけでなくファイルシステムへのアクセスの分離を実現することもできます。Dockerがコンテナを作成する際に作られる名前空間
Dockerがコンテナを作成する際、以下のような名前空間が作成されます。
- ipc名前空間:Inter Process Communication名前空間と呼ばれる内部的なプロセス間通信を分離する
- mnt名前空間:プロセスから見えるファイルシステムのマウント(外部の記憶媒体を扱うための方法)情報を分離し、chrootコマンドに近い動きをする
- net名前空間:ネットワークの制御を行う名前空間。net空間ごとにネットワークインターフェースを持つことができるため、複数のコンテナとホスト間でネットワーク通信を行うことができる
- pid名前空間:プロセスの分離に使用する。カーネルが制御しており、親PIDによる子PIDの制御などに利用される
- user名前空間:ユーザーIDとグループIDを分離する。user名前空間ごとに個別のユーザーIDとグループIDを保持できる
- uts名前空間:UTS(UnixTime-SharingSystem)名前空間と呼ばれる。ホスト名やNiSドメイン名(NISという仕組みで登場するコンピュータに付けた管理上の名前)などの分離に使用する
pid名前空間の分離
前述の通り、Dockerコンテナを実行するとDockerエンジンはそのコンテナに対する名前空間を作成します。
各コンテナは別々の名前空間で動作しており、アクセスはその名前空間に限定されます。
これによって、ホストOSからは複数の名前空間に属するプロセスが一様に動いているよう見えるのに対して、個々の名前空間の中ではその名前空間に所属するプロセスしか見えていません。
物理基盤におけるPID名前空間とコンテナ基盤によるPID名前空間を図に表すとそれぞれ以下のようになります。コンテナを使用した名前空間分離の例
例えば、Dockerエンジンが稼働するホストOSによって、Webサーバーのhttpdサービスが稼働するコンテナとFTPサーバーのvsftpdサービスが稼働するコンテナの2つが起動しているとします。
ホストOSはhttpdデーモンにプロセスID(PID)として1000番を割り当て、vsftpdデーモンにPIDとして2000番を割り当てたとすると、ホストOSからはhttpdとvsftpdがプロセスとして稼働しているように見えています。
この時、httpdサービスが稼働するコンテナ内ではhttpdにPIDの1番が割り当てられ、vsftpdサービスが稼働するコンテナ内ではvsftpdにPIDの1番が割り当てられます。
ここでhttpdが稼働するコンテナとvsftpdが稼働するコンテナは別々のPID名前空間であるためそれぞれのコンテナ内ではお互いのプロセスを見ることができず分離された空間となっています。最後に
今回はdockerの名前空間による分離の中でも特にプロセスidの分離について調べましたが、その他にもファイルシステムの名前空間も同様にコンテナとして分離されます。また、ハードウェアのリソースの制限はcgroupsという仕組みで行っています。
Dockerを使用する前に、これらのDockerの仕組みを学ぶことで、実際にコマンドを叩く際にも内部で起こっていることのイメージもつきやすくなりました。参考
- 投稿日:2020-07-03T23:05:09+09:00
【Ubuntu】DockerでRuby on Railsの環境構築中にPermissionErrorが起きる
背景
https://docs.docker.com/compose/rails/
Ubuntu上から上記に従ってすすめると以下のコマンド実行時にでエラーが出る
$ docker-compose run web rails new . --force --no-deps --database=postgresql
【エラー内容】
PermissionError: [Errno 13] Permission denied: '***/myapp/tmp/db'
原因
rails new
実行時に作成されるmyapp/tmp/dbの所有者がrootになっているためコマンドが正常に動作しない模様解決策
myappディレクトリ直下で以下を実行し/tmp/dbの所有者をログイン中のユーザーへ変更
sudo chown -R $USER:$USER .
参考
- 投稿日:2020-07-03T16:47:20+09:00
ansible-bender でコンテナイメージを作成する
概要
Ansible と同じ書式で、コンテナイメージの作成手順を指定することができる ansible-bender を試してみた。
環境構築
手順書にしたがって、ansible-bender をインストールする。
$ sudo pip3 install ansible-benderansible-bender は内部的に buildah と podman を呼び出すので、手順書にしたがって、buildah と podman をインストールする。参考までに、私が使っている ansible playbook は以下の通り。
最小コンテナイメージを用意する
ansible-bender は Python がインストール済みのコンテナイメージが必要。だが、DockerHub で配布されている Debian イメージには、Python がまだ含まれていないので、このイメージに対しては ansible-bender は適用できない。
そこで、Python を含む最小のコンテナイメージを用意しておくと便利である。まず、以下のような Dockerfile を用意する。
DockerfileFROM debian:buster MAINTAINER Anatano Namae <namae@example.jp> RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y python3 && apt-get clean次に、以下のコマンドを実行して、debian-python3 という名前のコンテナイメージを作成する。
$ sudo podman build -t debian-python3:buster -f Dockerfile作成が完了すれば、以下のようにイメージ一覧に表示されるようになるはずである。
$ sudo podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/debian-python3 buster 7b80a3ed5790 42 seconds ago 199 MBansible-bender でコンテナイメージを作成する
コンテナイメージを作成するための手順書を、Ansible とほぼ同じ形式で用意する。
sample.yml- name: ntpd container hosts: all vars: ansible_bender: base_image: debian-python3:buster target_image: name: debian-ntpd:buster tasks: - name: install ntpd apt: name: ntpd state: latest手順書の書き方詳細については、ansible-bender のドキュメントを参照。
後は、以下のコマンドを実行すれば良い。
$ sudo ansible-bender build sample.yml正常にコンテナイメージの作成が完了すると、以下のコマンドでイメージができていることが分かる。
$ sudo podman images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/debian-ntpd buster ab26e1f28761 13 minutes ago 223 MBなお、最新の ansible-bender はバックエンドとして buildah のみをサポートしており、docker サポートは打ち切られている。そのため、直接 docker 形式のコンテナイメージを作成することは できない 。
インストールするパッケージが多い場合は、構築時間短縮のために
HTTP_PROXY
を指定するなど細かい調整が必要になるのだが、設定方法の調査が完了していない。作成したコンテナイメージを docker 形式に変換する
buildah と docker の両方がインストールされていれば、以下のコマンドで podman のコンテナイメージを、同一ホストの docker に転送することができる。
$ sudo buildah push debian-python3:buster docker-daemon:debian-python3:busterその上で、以下のように docker save と docker load を組み合わせれば、他ホストに docker コンテナイメージを転送できる。
$ sudo docker save debian-python3:buster | ssh docker.example.net sudo docker load
- 投稿日:2020-07-03T12:58:22+09:00
Arch Linux に kubeadm で開発用 Kubernetes クラスタを構築してみる
はじめに
最近お仕事で Kubernetes クラスタをぼちぼち触り始めました。そういうわけで手元にも開発用に Kubernetes クラスタがほしくなってくるわけですが、いつも使っているのは Arch Linux です。そこで、今回は Arch Linux に kubeadm を使って Master と Worker が同居する1台構成の Kubernetes クラスタを作る方法を紹介します。
インストール前の準備
もちろん Arch Linux のインストールが必要ですが、下記の点によく注意してください。
Swap の無効化
Swap が有効化されていると kubeadm は動かすことができません。最近マシンスペック向上のおかげで Swap を作る機会は減っているかもしれませんが、もし有効にしているのであれば無効化しておいてください。
ファイルシステムについて
kubeadm では btrfs をサポートしていません。もし btrfs でディスクをフォーマットしてしまっていたら、ext4 で作り直す必要があります。要するに、クリーンインストールです......
AUR について
今回紹介する方法は AUR にしか存在していないパッケージを利用します。AUR の利用を容易にするために AUR が扱えるパッケージマネージャー (yayなど) を導入しておくことをオススメします。
Docker のインストール
何はともあれコンテナ基板として Docker をインストールします。
Docker パッケージのインストール
ひとまずリポジトリからインストールして、sudo コマンドなしでも動くように設定しておきます。
usermod
コマンドを実行したら一度ログアウトして再ログインします。$ yay -S docker $ sudo usermod -a -G docker ${USER}設定の変更
/etc/docker/daemon.json
ファイルを新規生成して、以下の内容を書き込んでください。特にストレージドライバの設定は重要です。$ sudo mkdir /etc/docker $ sudo vim /etc/docker/daemon.json/etc/docker/daemon.json{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" }Docker デーモンの起動
あとはさっくりと systemd で Docker デーモンを起動しておきましょう。
$ sudo systemctl daemon-reload $ sudo systemctl enable --now dockerKubernetes のインストール
さて、準備が済んだのでいよいよ Kubernetes をインストールしていきます。
必要なパッケージの導入
Kubernetes そのものの動作や操作に必要な
kubectl
kubelet
kubeadm
を AUR からインストールします。また、後ほどクラスタ内ネットワークの構築に必要となるcni-plugins
もここで入れておきます。$ yay -S kubectl-bin kubelet-bin kubeadm-bin cni-plugins-binまた、Kubernetes が必要とするネットワーク関連のパッケージを追加でインストールしてください。
$ yay -S ethtool ebtables socat conntrack-toolskubelet の起動
ここまで終わったら kubelet を起動する設定を有効化します。
$ sudo systemctl enable --now kubeletMaster node の初期化
いよいよ Master node を初期化して、動作できるようにします。下記コマンドを流し込んで数分待つとできあがりです。なお
pod-network-cidr
に渡している IP アドレスですが、後ほど flannel (内部ネットワーク) を有効化する際に必要なものです。また、flannel のドキュメントによるとこの IP アドレスで固定されているようですので、他のものに変えたりはしないでください。$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16構築が終わったら表示されたコマンドを使って管理者として
kubectl
コマンドを実行できるようにしていきます。$ mkdir -p $HOME/.kube $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config $ sudo chown $(id -u):$(id -g) $HOME/.kube/config最後に Master node にコンテナを建てられるように設定を変更します。通常はセキュリティ面から Master node にユーザーがコンテナを建てることはできませんが、今回は手元開発用の1台構成ですので、それを許可するようにしておきます。もちろん、本番環境で使う Master node ではこの設定を有効にしないでください。
$ kubectl taint nodes --all node-role.kubernetes.io/master-Flannel のインストール
クラスタ内部ネットワークを構築するため Flannel をインストールします。以下のコマンドを流し込んでください。
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml動作テスト
Nginx のコンテナを建てて、クラスタが動作しているかどうか確認してみましょう。適当なディレクトリに
nginx.yaml
を下記の内容で用意してください。$ vim nginx.yaml
yamlファイルの中身をざっくり説明すると、Nginx のコンテナを2個立てて、8080番ポートを露出させるという設定になっています。
nginx.yamlapiVersion: v1 kind: Service metadata: name: nginx spec: type: LoadBalancer selector: app: nginx ports: - name: nginx port: 8080 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80ファイルが書けたらクラスタにデプロイしてみましょう。
$ kubectl apply -f nginx.yaml数分待って Pod と Service の一覧を取得します。定義通り Nginx のコンテナが2つ
READY
状態になっており、service/nginx
ができあがっていることが確認できます。$ kubectl get pods,services NAME READY STATUS RESTARTS AGE pod/nginx-d46f5678b-sm685 1/1 Running 0 3m18s pod/nginx-d46f5678b-whdbn 1/1 Running 0 3m18s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15m service/nginx LoadBalancer 10.111.243.255 <pending> 8080:30044/TCP 4m14sNginx にアクセスしたいときは
localhost
からPORT(S)
欄の右側に表示されている方のポート(今回であれば30044)にアクセスしてあげると繋がります。ここまで確認できればOKです。$ curl http://localhost:30044/ <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <!-- 後略 -->おわりに
この記事では Arch Linux に kubeadm を使って Kubernetes クラスタを構築する方法を紹介してきました。kubeadm を使えばこうして簡単に開発用のクラスタも構築できます。また、公式ドキュメントではサポートされていない Arch Linux でも以外と平気で動かせてしまうということも明らかになりました。皆さんもぜひご自分の Arch Linux 環境に Kubernetes 開発環境を用意して快適な開発者生活を送ってください。
- 投稿日:2020-07-03T12:27:33+09:00
Golang+Docker+LINE BOT+QiitaAPI+herokuで何か作ろうとした話
記事概要
Golang+Docker+LINE BOT+QiitaAPI+herokuで何か作ろうとしてハマった事とか色々まとめるだけの記事。
筆者について
- Golang初見。
- linebot、herokuはPHPとRailsで触った事がある。
- Dockerは便利すぎてハマりだしてる。
リポジトリ
https://github.com/YanaPIIDXer/QiitaLineBot
実際に動作させるherokuとLINEのアカウントは非公開で。。。
「どんなものを作るか」「設計はどうするか」はここのREADMEに。採用したもの
- gin
- Webサーバライブラリ。
- Realize
- ホットリロード。
- 後述。
Golangのホットリロード
「コードを書き換えると自動でビルドが走って、後はブラウザを更新するだけ」と言う機能。
導入
Realizeを使ってGoでホットリロードを実現する
上記記事を参考にRealizeを導入。
・・・が、Realize起動時のビルドは走ってるっぽいがホットリロードは機能しない。legacyの有効化
どうもRealizeをDocker(コンテナ)上で動作させるには、legacyを有効化する必要がある模様。
ここを参照。
.realize.yamlの記述を.realize.yamlsettings: legacy: force: true (省略)として再起動したら無事動作。
herokuへのdeploy
ポート
ローカル環境で立ち上げている時はポート3000を使用しているが、heroku上で動作させる場合はherokuが指定したポートで処理をしなければならない。
プログラム側の対応
環境変数「PORT」に値が入っていればheroku上での動作としてそのポートを、入っていなければローカルでの動作としてポート3000を利用するようにしてみよう。
Dockerfile# 以下の記述を追加。 ENV PORT=${PORT}main.go// サーバ起動部分。 var port = os.Getenv("PORT") // heroku上で動かす場合は指定されたPortじゃないと死なますよ。 if port == "" { port = "3000" } fmt.Println("Server Port:" + port) router.Run(":" + port)これでdeployしたら動・・・かなかった。
コンテナ構成の変更
色々調査しながら、ふとdocker-compose.ymlを眺めると・・・
docker-compose.yml(省略) ports: - "3000:3000" command: realize start --name='server' --run (省略)・・・
docker-compose.ymlports: - "3000:3000"お 前 か 。
ここもherokuが使ってるポートにしてやらないと動かないんじゃないかな・・・?色々調べて、以下の記事がヒット。
golang,docker,mysqlの環境をherokuにデプロイするこの記事にある通りにコンテナ構成を変更。
上記の記事ではdocker-compose.ymlをdockersディレクトリ内に配置しているが、同じようにすると動作せず。
多分どこかでパスに問題が出てるんじゃないかな・・・?未調査だけど。
面倒なのでルートディレクトリに別名(docker-compose-local.yml)で放り込むと動作するようになった。herokuにdeployして、ログを見た感じエラーとかも無さそう。
いざアクセス・・・が、動作せず。SSL対応
「あれ?」と思っておもむろに
heroku logs
を実行。
アクセス時にエラーになってた。
で、そのログをよく見ると・・・protocol=https・・・FxxK!!
こりゃまた頭の痛い問題だ・・・。
(今ここ。進展あれば更新予定。)
- 投稿日:2020-07-03T12:27:33+09:00
Golang+Docker+herokuで何か作ろうとした話
記事概要
Golang+Docker+herokuで何か作ろうとしてハマった事とか色々まとめるだけの記事。
筆者について
- Golang初見。
- herokuはPHPとRailsで触った事がある。
- Dockerは便利すぎてハマりだしてる。
採用したもの
- gin
- Webサーバライブラリ。
- Realize
- ホットリロード。
- 後述。
リポジトリ
https://github.com/YanaPIIDXer/QiitaLineBot
試行錯誤の経緯とかはここのコミットログを見れば大体分かる・・・かも。
ちなみに今回ブランチ分岐は無しの方向。Golangのホットリロード
「コードを書き換えると自動でビルドが走って、後はブラウザを更新するだけ」と言う機能。
導入
Realizeを使ってGoでホットリロードを実現する
上記記事を参考にRealizeを導入。
・・・が、Realize起動時のビルドは走ってるっぽいがホットリロードは機能しない。legacyの有効化
どうもRealizeをDocker(コンテナ)上で動作させるには、legacyを有効化する必要がある模様。
ここを参照。
.realize.yamlの記述を.realize.yamlsettings: legacy: force: true (省略)として再起動したら無事動作。
herokuへのdeploy
ポート
ローカル環境で立ち上げている時はポート3000を使用しているが、heroku上で動作させる場合はherokuが指定したポートで処理をしなければならない。
プログラム側の対応
環境変数「PORT」に値が入っていればheroku上での動作としてそのポートを、入っていなければローカルでの動作としてポート3000を利用するようにしてみよう。
Dockerfile# 以下の記述を追加。 ENV PORT=${PORT}main.go// サーバ起動部分。 var port = os.Getenv("PORT") // heroku上で動かす場合は指定されたPortじゃないと死なますよ。 if port == "" { port = "3000" } fmt.Println("Server Port:" + port) router.Run(":" + port)これでdeployしたら動・・・かなかった。
コンテナ構成の変更
色々調査しながら、ふとdocker-compose.ymlを眺めると・・・
docker-compose.yml(省略) ports: - "3000:3000" command: realize start --name='server' --run (省略)・・・
docker-compose.ymlports: - "3000:3000"お 前 か 。
ここもherokuが使ってるポートにしてやらないと動かないんじゃないかな・・・?色々調べて、以下の記事がヒット。
golang,docker,mysqlの環境をherokuにデプロイするこの記事にある通りにコンテナ構成を変更。(Realizeはローカルでのみ動作させるように。)
上記の記事ではdocker-compose.ymlをdockersディレクトリ内に配置しているが、同じようにすると動作せず。
多分どこかでパスに問題が出てるんじゃないかな・・・?未調査だけど。
面倒なのでルートディレクトリに別名(docker-compose-local.yml)で放り込むと動作するようになった。herokuにdeployして、ログを見た感じエラーとかも無さそう。
いざアクセス・・・が、動作せず。
SSL絡みかと思ったが、普通にhttpでアクセスしても同じ結果だった。No web processes running
ログをみた所、「No web processes running」と言うエラーメッセージが。
これを調べた所、以下の記事に行き着いた。
【Heroku】デプロイ後にcode=H14 desc="No web processes running"記事にある通り、
heroku ps:scale web=1
を叩いたところ、動作はしなかったもののエラーメッセージが変わった。
(記事では「このコマンドを叩いただけでは動作せず、heroku.ymlが必要」との事だったが、自分はRealizeをローカルでのみ動作させるようにする過程で作成済みなのでこのコマンドだけでOKだった。)App crashed
何かクラッシュしたらしい。
このエラーメッセージで調べたところ、以下の記事がヒット。
HerokuでApplicationErrorが発生したときの対処法この記事に書かれていたコマンドで再起動。しかし変わらず。
そこからログを少し遡ると、
.env Load Failed.
。
「・・・これってさ、.envの読み込み失敗時にそのままエラーとしてreturnしてた時のログだよね?herokuへのdeploy作業の過程でそのエラー処理は撤去したんだけど・・・」
「何かしらの原因で過去のやつが動作しているのでは?」と言う疑いが。再度deployしてアクセス→router.Runメソッドが走るところまでは確認したがApp crashは解消されず→再度上記記事のコマンドで再起動→
.env Load Failed.
気になる事としては、「herokuへのdeploy時、releaseから処理が返ってこない」と言う事。(毎回Ctrl+Cしてる。)
「コマンドがフォアグラウンドで走ってるとか・・・?」とか考えて、適当にENTRYPOINTをlsに変えてみても結果は同じ。
で、ENTRYPOINTがlsのものが動いてる(・・・と思い込んでいる)状態でheroku logs
。
普通にGoのプログラムが動いていた。
・・・まぁ、「処理が返ってこない」と言いながらCtrl+Cで強制的に抜けてるんだからそりゃそうだわな。アプリケーションの作り直し
とりあえず
heroku destroy
で一旦破棄。
heroku create
で作り直す。
再度pushしてアクセスすると無事動作。
app crashedの原因はよく分からないが、一旦作り直してみるのも手ではある。(今回は「過去のプログラムが動作した」と言う事から、「ゴミが残っている」と言う事が明白だったので。)ちなみにここでdeploy方法を
git push heroku master
からheroku container:push web
→heroku container:release web
にも変更している。
(自分はdocker-composeを使ったビルドしかした事が無く、docker-composeを使わずにコンテナを単体で動かす時にどうやってコンテナ名を付けるのかが分からなかった為。。。これもここで覚えた。)最後に
「GolangをDockerでインストールし、heroku上で動作させる」まででハマったところを色々まとめてみた。
冒頭に書いたリポジトリでのアプリケーション開発活動自体はまだまだ継続するものの、今回の表題である「Golang+Docker+heroku」での動作までは進み、「後はGolang内で色々と実装するだけ」と言う段階に至ったので、ひとまずこの記事は完結とする。
(それに伴い、余計な記述も撤去。)ここまで来るのに数日を要したが、その分多くのことをインプットできたように思える。
そこでブチ当たった様々な問題とその解決策をなるべくリアルタイムで更新してきた。
「誰かに伝える事」は正直苦手だから色々と拙い所はあると思うが、こういった記事でも役に立ってくれれば幸いである。
- 投稿日:2020-07-03T06:08:05+09:00
fluentdでdockerコンテナのログをファイルごとに出し分けてみたお話
fluentdとは
fluentdとはログ収集ツールです。
様々な形式のinputからログを収集でき、それを必要に応じて整形・加工してログ出力できます。環境
- OS:debian 10.3
- docker:19.03.11
- docker-compose:1.16.1
- fluentd:1.3.2
今回の目標
今回の目標はサーバーのログを標準出力からファイル出力へ切り替えることです。
さらに、ファイル出力も全てのログを出力するログファイルと、エラーログのみを出力するエラーログファイルの2種類作成していきます。fluentdの設定ファイルについて
デフォルトでは、
/fluentd/etc/fluent.conf
が読み込まれます。tag
fluentdではログをtagというもので管理していきます。
このtagを使って設定を任意のログのみに適用できたりします。ディレクティブ
fluentdの設定はディレクティブと呼ばれるもので設定していきます。
ディレクティブには下記のようなものがあります。
- source: ログの入力を設定するディレクティブ。
- match: ログの出力を設定するディレクティブ。引数にtagを指定。
- filter: ログに対して行う処理を設定するディレクティブ。引数にtagを指定。
- label:
@label
でラベル名を設定することで、<label ラベル名>
の設定にルーティングできる。下記はmatchディレクティブの例です。
<match myapp.access> @type file path /var/log/fluent/access </match>
myapp.access
の部分がtagになります。
@type
の部分が次に説明する、プラグインを指定しています。設定ファイルの主なディレクティブの流れとしては、
< source > → < filter > や < label >など → ...... → < match >
となることを頭に入れておくと設定ファイルの読み書きがしやすくなると思います。
参考:ディレクティブ 公式ドキュメントプラグイン
fluentdには標準のプラグインが豊富にあります。
プラグインは先ほどのディレクティブ内に@type
で指定することで利用できます。
このプラグインを使うことで様々な形式でログを入出力できたり、ログを加工したりできるようになります。下記は filterディレクティブ のプラグインの一部です。
- record_transformer:ログにフィールドを追加したり削除したりできる。
- grep:正規表現でマッチするログだけを通したり、マッチするログを出力しないようにしたりできる。
- parser:ログ内のフィールドをjsonなどの形式を指定してパースすることができる。
今回使う各ファイル
ファイル階層
├── app │ └── main.go ├── docker │ ├── app │ │ ├── app │ │ └── Dockerfile │ ├── docker-compose.yml │ └── fluentd │ ├── config │ │ └── fluent.conf │ ├── Dockerfile │ └── log │ ├── alert.buf │ └── log.buf └── Makefileapp/main.go
今回はこちらの超簡易サーバーを使っていきたいと思います。
main.gopackage main import ( "fmt" "net/http" "github.com/sirupsen/logrus" ) func main() { // server起動 http.HandleFunc("/sample", outputQueryString) http.ListenAndServe(":8080", nil) } func outputQueryString(w http.ResponseWriter, r *http.Request) { log := logrus.New() // 処理開始のログ log.Infoln("Start!") // クエリストリングからキーが name の値を取得 qs := r.URL.Query().Get("name") // キーが name の値がない場合のエラー処理 if qs == "" { log.Errorln("Query string is nothing.") w.WriteHeader(http.StatusBadRequest) return } // name があった場合の出力 fmt.Fprintf(w, "Your name is %s", qs) log.Infoln("Complete!") }このサーバーのログをfluentdで収集します。
こちらのサーバーは、
- (IPアドレス):8080/sample?name=XXX にアクセスすると、「Your name is XXX」を返す
- (IPアドレス):8080/sample?key=XXX のようにクエリストリングのキーに name の値がない場合、エラーログを出力し、400エラーを返す
という処理内容になってます。
出力されるログは、
- アクセスされたときに処理開始のログ
Start!
を出力- key が name のクエリストリングがなかった場合、エラーログ
Query string is nothing.
を出力- 処理が正常終了した場合、処理完了のログ
Complete!
を出力となっています。
なので、ログファイルには1, 2, 3のログが、エラーログファイルには2のログのみが出力されることになります。docker/app/app
app/main.go の実行ファイル。
こちらをappのコンテナ内にコピーして実行します。docker/app/Dockerfile
DockerfileFROM alpine:latest COPY app /bin CMD "app"今回、appのdockerイメージはalpineを使用しています。
alpineを選択した理由はdockerイメージの容量が小さくなるからです。
詳しくはこちらの記事を参考にしてみてください。
Alpine Linux で Docker イメージを劇的に小さくするDockerfileの内容としては、先ほどのappの実行ファイルをコンテナ内の /bin ディレクトリにコピーして実行しているだけです。
docker/fluentd/config/fluent.conf (fluentd設定ファイル)
いよいよ、本命のfluentdの設定ファイルです。
ちょっと長いかもしれませんが、ブラウザバックするのは待ってください。
わかります。わたしも設定ファイルアレルギーの重症者ですから。
ですが、安心してください。
部分に分けて説明をしているので、どうか我慢して少し読み進めてみてください。
わからないところがあればコメントいただければできるだけ(アレルギー重症者レベルで)回答します。fluent.conf<source> @type forward @label @mainstream port 24224 </source> <label @mainstream> <match docker.**> @type copy <store> @type relabel @label @all_log </store> <store> @type relabel @label @err_log </store> </match> </label> <label @all_log> <match **> @type file path /var/log/fluent/log_*.log format ltsv buffer_type file buffer_path /var/log/fluent/log.buf symlink_path /var/log/fluent/log_current time_slice_format %Y%m%d flush_at_shutdown true append true </match> </label> <label @err_log> <filter **> @type grep <regexp> key log pattern /level=(warn|error)/ </regexp> </filter> <match **> @type file path /var/log/fluent/alert_*.log format ltsv buffer_type file buffer_path /var/log/fluent/alert.buf symlink_path /var/log/fluent/alert_current time_slice_format %Y%m%d flush_at_shutdown true append true </match> </label>設定ファイルの内容説明
設定ファイルの内容を上から分けて見ていきます。
<source> @type forward @label @mainstream port 24224 </source>この sourceディレクティブでは、inputの設定を定義しています。
取得したログに対して@mainstream
というラベルを設定しているので、この次は<label @mainstream>
の設定に飛びます。<label @mainstream> <match docker.**> @type copy <store> @type relabel @label @all_log </store> <store> @type relabel @label @err_log </store> </match> </label>この
<label @mainstream>
では、ログの複製を行っています。
@type copy
でログの複製ができます。
なぜ複製しているのかというと、この後ログを、全てのログが出力されるログファイルと、エラーログだけが出力されるエラーログファイルに分けたいからです。
複製したログには@all_log
、@err_log
というラベルを再設定しているので、
それぞれ<label @all_log>
、<label @err_log>
のディレクティブに飛びます。<label @all_log> <match **> @type file path /var/log/fluent/log_*.log format ltsv buffer_type file buffer_path /var/log/fluent/log.buf symlink_path /var/log/fluent/log_current time_slice_format %Y%m%d flush_at_shutdown true append true </match> </label>この
<label @all_log>
では、ログファイルの出力設定を行っています。
path
ではファイルの出力先を指定しています。* は後述の time_slice_format で指定したフォーマットで日付や日時が入ります。buffer_type
ではファイルに出力する前のバッファリングをfileに出力するか、メモリに溜めておくかを設定できます。メモリの方が処理は速いですが、fluentdが途中で落ちるとログが消失する可能性があります。buffer_path
ではバッファをfile出力にした際の出力先を指定しています。symlink_path
ではファイル出力しているバッファへのシンボリックリンクを設定しています。これにより、バッファが出力されているファイル名がどんなものであれ、同じファイル名でアクセスできるようになります。time_slice_format
ここで設定したフォーマットでファイル名の * が置き換えられます。デフォルトでは %Y%m%d なので日付単位でファイルが生成されますが、ここの設定を時間単位にすればファイルが1時間ごとに生成されるようになるので、ファイルの生成単位を設定しているとも言えます。flush_at_shutdown
では、fluentdがシャットダウンされた際に最後に1度だけバッファの内容を出力するかどうかを設定しています。trueなら出力を試します。append
では、既にファイル出力先ファイルがある場合、追記するか別ファイルに出力するかを設定しています。trueなら追記します。<label @err_log> <filter **> @type grep <regexp> key log pattern /level=(warn|error)/ </regexp> </filter> <match **> @type file path /var/log/fluent/alert_*.log format ltsv buffer_type file buffer_path /var/log/fluent/alert.buf symlink_path /var/log/fluent/alert_current time_slice_format %Y%m%d flush_at_shutdown true append true </match> </label>この
<label @err_log>
では、エラーログファイルの出力設定を行っています。
filterディレクティブでは、grepプラグインを使い、ログのフィルタリングを行っています。
grepプラグインでは、フィールドのキーを設定して、それに対して正規表現で通すログ通さないログを設定できます。
この設定では、 log というキーに対してlevel=warn
かlevel=error
という値を持つログのみを通します。
matchディレクティブについては、上記ログファイル出力設定と重複するので割愛します。以上が、fluent.confの設定内容です。
label
のおかげで流れを掴みやすく、他の設定ファイルより読みやすいのではと思います。fluentdのDockerfile
DockerfileFROM fluent/fluentd:latestdockerイメージで、fluentdを指定しているだけです。
アプリのcompose.yml
docker-compose.ymlversion: "3.3" services: app: build: ./app container_name: app restart: always ports: - "8080:8080" logging: driver: "fluentd" options: fluentd-address: "localhost:24224" tag: "docker.{{.Name}}" depends_on: - fluentd fluentd: build: ./fluentd container_name: fluentd volumes: - ./fluentd/config:/fluentd/etc - ./fluentd/log:/var/log/fluent restart: always ports: - "24224:24224"appの logging というところでログ出力先の設定を行っています。
こちらのように driver に fluentd を指定することで、fluentdでログを収集できるようになります。
optionsではfluentdのコンテナへの接続とログのtagの設定をしています。fluentdのvolumesでは、2つのディレクトリをマウントしています。
1つめの./fluentd/config
では、デフォルトのfluent.confが格納してあるディレクトリをマウントすることで、設定ファイルを上書きしています。
2つめの./fluentd/log
では、fluentdのログファイルが出力されている /var/log/fluent をマウントすることで、ログファイルをローカルの /docker/fluentd/log のディレクトリから取得できるようにしています。実際に起動してみる
それでは、実際に起動してみます。
$cd docker/; docker-compose up -d --build Creating network "docker_default" with the default driver Building fluentd Step 1/1 : FROM fluent/fluentd:latest ---> 9406ff63f205 Successfully built 9406ff63f205 Successfully tagged docker_fluentd:latest Building app Step 1/3 : FROM alpine:latest ---> a24bb4013296 Step 2/3 : COPY app /bin ---> a57975cbc6b8 Step 3/3 : CMD "app" ---> Running in fa1f4446976e Removing intermediate container fa1f4446976e ---> 515558e9745f Successfully built 515558e9745f Successfully tagged docker_app:latest Creating fluentd ... Creating fluentd ... done Creating app ... Creating app ... done $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b76f4a1c2bdd docker_app "/bin/sh -c \"app\"" 57 seconds ago Up 56 seconds 0.0.0.0:8080->8080/tcp app b732ea83c8e7 docker_fluentd "/bin/entrypoint.sh …" 58 seconds ago Up 57 seconds 5140/tcp, 0.0.0.0:24224->24224/tcp fluentdfluentdとサーバーのコンテナが起動できました。
では、アクセスしてみましょう。
まずは、 (IPアドレス):8080/sample?name=Qiita でアクセスしてみます。
クエリストリングで指定したQiita
がちゃんと表示されてますね。続いて、 (IPアドレス):8080/sample?key=Qiita でアクセスしてみます。
こちらもちゃんとエラーが返されてます。サーバーは意図通り動いてそうですね。
続いて、ログについて見ていきます。
log_yyyymmdd.log には全てのログが出力されます。
alert_yyyymmdd.log にはエラーログのみが出力されます。
実施日が2020/06/29なので yyyymmdd は 20200629 になります。log_20200629.logsource:stderr log:time="2020-06-29T14:18:06Z" level=info msg="Start!" container_id:1b3e5a8deaaa2322bec250db7a47d67f49dd5654bc037af1730e31db1c4af74d container_name:/app container_id:1b3e5a8deaaa2322bec250db7a47d67f49dd5654bc037af1730e31db1c4af74d container_name:/app source:stderr log:time="2020-06-29T14:18:06Z" level=info msg="Complete!" source:stderr log:time="2020-06-29T14:18:09Z" level=info msg="Start!" container_id:1b3e5a8deaaa2322bec250db7a47d67f49dd5654bc037af1730e31db1c4af74d container_name:/app container_id:1b3e5a8deaaa2322bec250db7a47d67f49dd5654bc037af1730e31db1c4af74d container_name:/app source:stderr log:time="2020-06-29T14:18:09Z" level=error msg="Query string is nothing."alert_20200629.logcontainer_id:1b3e5a8deaaa2322bec250db7a47d67f49dd5654bc037af1730e31db1c4af74d container_name:/app source:stderr log:time="2020-06-29T14:18:09Z" level=error msg="Query string is nothing."log_20200629.log を見てみるとの1行目と3行目には処理開始のログが出力され、2行目と4行目にはそれぞれ正常終了時のログとエラー時のログがすべて出力されていますね。
一方、alert_20200629.log の方には log_20200629.log の4行目と同じログ(エラーログ)が出力されています。
これでエラー時のログのみ見たい場合は alert_20200629.log を見ればよくなりましたね。おわりに
今回はfluentdの設定ファイルの概要と実際に使ってみた実例を紹介しました。
ここまで読んでいただいた方には、fluentdの設定ファイルがそこまで難解ではないというのが少しは伝わったのではないでしょうか?
設定ファイルアレルギーの重症者であるわたしでも簡単な設定ファイルについては記述できるようになりました。
みなさんもぜひこの記事をきっかけにfluentdデビューをしてスマートなログ収集ライフを!
- 投稿日:2020-07-03T01:31:45+09:00
docker コマンド集
Use Docker command line client
docker コンテナ実行
docker run [image] image = docker hubなどにあるもの e.g.) hello-worlddocker コンテナ実行(インタラクティブモード)
docker run -t -i [image] [cmd] e.g.) docker run -t -i ubuntu /bin/bashdocker コンテナ実行(background + Port開放 + name割当)
docker run -d -P --name hello-world-app training/webapp python app.py --- -dはdeamon(background) -Pはネットワークポートの開放(自動割当) --nameは名前の割当 --- docker ps で確認されて割り当てられたポートで内部からアクセスできる e.g.)curl http://localhost:<port>/docker コンテナ実行(background + Port開放 + name割当)
docker run -d -p 80:5000 --name webapp training/webapp python app.py --- -pオプションはポート番号を手動で割り振れる --- docker ps で確認されて割り当てられたポートで内部からアクセスできる e.g.)curl http://localhost:80/ 実はこれでもう80番ポートを開放しているので外部からアクセスできる(httpは80番なので、80を省略) e.g.)http://[このサーバーのグローバルIP]/ ←これでブラウザからアクセス可能docker コンテナ停止
docker stop -t 0 [name] nameはimage_nameではなく、docker_psでみれるname -t は停止までの時間。指定しなければ10, 0だと即時終了docker コンテナ実行中確認
docker psdocker コンテナ一覧確認(未実行含む)
docker ps -adocker image 削除
docker rm [image_layer_name] image_layer_name = docker ps -a 出てくるものdocker image 取得
docker image pull [image_name] e.g.) docker image pull ubuntudocker image 取得(特定のver)
docker image pull [image_name]:[version] e.g.) docker image pull ubuntu:14.04docker image一覧
docker image lsdocker image 調査
docker inspect [name]docker image 調査(フィルター)
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [name]docker exec(既に起動しているコンテナにコマンド実行)
docker exec [name] ps aux これだと ps auxを実行させるdocker exec(既に起動しているコンテナにコマンド実行をインタラクティブモードで)
docker exec -it [name] bash root@249476631f7d:/opt/webapp# ps auxdocker ファイルコピー
docker cp [origin] [copy] e.g.) docker cp webapp:/opt/webapp/app.py . この場合、webappのコンテナの中にあるapp.pyをlocalにコピー --- e.g.)docker cp app.py webapp:/opt/webapp/ この場合、localにあるapp.pyをwebappのコンテナの指定の場所にコピーその他
Persisted Volume
accessModes PVのアクセスモードを指定する。アクセスモードは現在以下の3種類。 ReadWriteOnce : 単一Nodeで読み書きが可能 ReadOnlyMany : 複数Nodeで読み込みが可能 ReadWriteMany : 複数Nodeから読み書きが可能