- 投稿日:2019-12-22T23:56:14+09:00
Docker+GitHub+HerokuでCI/CDっぽく
やった事
前に記事で公開したQiitaタグ自動ジェネレータをWebアプリとして手軽に試せる様に公開しました。
ついでにGitHubでソースも晒しています。https://auto-create-qiita-tags.herokuapp.com/
※Freeプランなので30分間アクセスがないと休眠してしまいます。少し時間がかかる時があるのはご愛嬌という事で...
全体図
Webアプリを公開するにあたりHerokuを使いました。GitHubとも連携させたのでこんな感じです。
GitHubにソースをpushすると自動的にHerokuでビルドが走りDockerコンテナのデプロイまでやってくれます。仕事での開発だと今時CI/CDなんて当たり前だと思いますが、個人でお金をかけずに環境作ってみたという感じです。Herokuって?
アプリケーションの開発から実行、運用までのすべてをクラウドで完結できるPaaSです。(公式サイトより)
結構前からあるサービスで知名度も高く知っている人も多いと思います。
以下を魅力に感じ今回使いました。
- 無料でWebアプリを公開することが出来る ※稼働時間など一部制約あり
- GitHub連携できる
- Docker対応している(Heroku dyno)
GitHub連携するには?
Herokuの画面から設定するだけ、すごく簡単。
Automatic deploys
を有効にするだけで、GitHubにpushされたらHerokuにアプリをデプロイするところまでやってくれます。
Dockerビルドするには?
連携したGitHubにDockerfileはもちろんですが、
heroku.yml
という設定ファイルが必要になります。
今回用意したheroku.ymlはこちら。heroku.ymlbuild: docker: web: Dockerfile色々出来るらしいですが、単純にDockerfileからビルドするだけなのでシンプルな内容となってます。
詳細な書き方は公式ドキュメントを見てみてください。ちなみにDokerfileはこちら。
PythonのWebアプリケーションフレームワークであるflask
を使いました。FROM python:3-alpine WORKDIR /work RUN wget http://gensen.dl.itc.u-tokyo.ac.jp/soft/pytermextract-0_01.zip RUN unzip pytermextract-0_01.zip RUN cd pytermextract-0_01 && python setup.py install RUN apk update RUN apk --no-cache add git gcc libc-dev libxml2-dev libxslt-dev RUN git clone https://github.com/fukumasa/auto-create-qiita-tags WORKDIR /work/auto-create-qiita-tags RUN pip install -r requirements.txt ENV FLASK_APP /work/auto-create-qiita-tags/app.py CMD flask run -h 0.0.0.0 -p $PORTHerokuでは起動時のポートがPORTという環境変数に格納されているため、flaskアプリケーション起動時に
$PORT
でポート指定しています。
これでコンテナがビルドされるとCMD
の内容が実行されます。起動したDockerアプリには80ポートでアクセスするため、おそらくですが、コンテナ内部の
$PORT
ポートとコンテナが稼働しているサーバの80ポートが自動的にフォワーディングされていると思われます。
Dockerコンテナをdocker run
で実行する際は、通常-p 80:5000
のようにポートフォワーディングを明示的に行う事が多いと思いますが、Herokuではこの辺りはあまり意識しなくても良さそうです。まとめ
Herokuを使ってDockerアプリを公開してみました。
GitHubを使えば、既存のソース+heroku.ymlで手軽に連携することが出来ます。
そもそもHerokuの使い方とかより詳細なコマンドとかは以下の参考ページを見てみてください。参考ページ
- 投稿日:2019-12-22T23:26:58+09:00
Dockerでnpmを使ってscssを書く
Dockerでnpmを使う。
普段のちょっとした開発を試すときとか、scss使って効率よくしたいなと思ったので、備忘録です。
前準備としてはこちらが必要です。
Dockerで開発環境構築(Mac + Nginx + PHP-FPM)docker-compose.ymlの編集
追加
node: image: node:latest ports: - "8000:80" volumes: - ./:/src working_dir: /src
node
の部分は好きな名前に変更してください。
latest
の部分は任意のversionに変更可能です。node install
$ docker-compose run --rm node npm initpackage.jsonが作成されます。
sassをインストール
sassをコンパイルできる
node-sass
をインストール。$ docker-compose run --rm node npm install node-sasspackage.jsonへの記述
package.json"scripts": { "css/scss": "node-sass src/scss/style.scss -o src/dist/css --output-style expanded", }scssファイルを作りstyle.scssの中にscss記法で何か書きます。
その後npmを実行します。
$ docker-compose run --rm node npm run css/scsssrcディレクトリの中に
dist
でディレクトリが作成されるはずです。
このdistディレクトリの中にcss/style.cssがあるはずです。それがコンパイラされたstylesheetです。
以上になります。
- 投稿日:2019-12-22T23:26:58+09:00
Dockerでscssを書く
Dockerでscssを書く
普段のちょっとした開発を試すときとか、scss使って効率よくしたいなと思ったので、備忘録です。
前準備としてはこちらが必要です。
Dockerで開発環境構築(Mac + Nginx + PHP-FPM)docker-compose.ymlの編集
npmを使います。
追加
node: image: node:latest ports: - "8000:80" volumes: - ./:/src working_dir: /src
node
の部分は好きな名前に変更してください。
latest
の部分は任意のversionに変更可能です。node install
$ docker-compose run --rm node npm initpackage.jsonが作成されます。
sassをインストール
sassをコンパイルできる
node-sass
をインストール。$ docker-compose run --rm node npm install node-sasspackage.jsonへの記述
package.json"scripts": { "css/scss": "node-sass src/scss/style.scss -o src/dist/css --output-style expanded", }scssファイルを作りstyle.scssの中にscss記法で何か書きます。
その後npmを実行します。
$ docker-compose run --rm node npm run css/scsssrcディレクトリの中に
dist
でディレクトリが作成されるはずです。
このdistディレクトリの中にcss/style.cssがあるはずです。それがコンパイラされたstylesheetです。
以上になります。
- 投稿日:2019-12-22T23:19:49+09:00
Docker の Rootlessモードを試してみた
前書き
ユーザが、Dockerを使用する際に、/var/run/docker.sock へのアクセス権限が問題になることがありました。
$ sudo ls -l /var/run/docker.sock srw-rw---- 1 root docker 0 12月 15 19:07 /var/run/docker.sock対処方法として、sudoを使う方法やユーザをdocker グループに入れる方法がありました。しかし、いずれもひと手間が面倒であったり、セキュリティ面に問題ありで、良い方法ではありませんでした。
1つの解決策として、Docker 19.03から下記のRootlessモードが行えるようになりました。
Docker 19.03新機能 (root権限不要化、GPU対応強化、CLIプラグイン…)
簡単に説明すると、各ユーザ用にDockerの環境を作成します。そのためDockerを使用するユーザ毎に、RootlessモードのDockerのインストールが必要です。
RootlessモードのDockerを実際にインストールした時に気が付いたことを書いています。
以下では、「Rootless Docker」と記述しています。試した環境
- CentOS Linux release 8.0.1905 (Core)
※CentOSは、「最小限のインストール」でインストールした直後の状態です。
この記事の中でインストールするRootless Dockerのバージョン
- Client: Docker Engine - Community master-dockerproject-2019-12-11
- Server: Docker Engine - Community Engine master-dockerproject-2019-12-11
前準備
テスト用にアカウントを2つ作成しています。
$ sudo useradd user01 $ sudo useradd user02 $ sudo passwd user01 $ sudo passwd user02Rootless Dockerのインストール手順
インストールは、下記のコマンドで完了します。
ユーザ権限で実行できます。$ curl -fsSL https://get.docker.com/rootless | sh $ echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bash_profile $ source ~/.bash_profile $ docker container run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/「Hello from Docker!」が表示されたら成功です。
次に、OSを再起動したとき自動起動するようにします。$ vi .config/systemd/user/docker.servicedocker.serviceファイルの最後の行を変更します。
multi-user.target のままだと、自動起動しなかったです。[Install] WantedBy=multi-user.target ↓ [Install] WantedBy=default.target自動起動の設定を続けます。
$ systemctl --user daemon-reload $ systemctl --user enable docker Created symlink /home/user01/.config/systemd/user/multi-user.target.wants/docker.service → /home/user01/.config/systemd/user/docker.service. $ sudo loginctl enable-linger user01これで、OS再起動時にRootless Dockerが起動するようになります。
インストール後の確認
ディレクトリ・ファイルを確認
インストールを完了すると、以下のディレクトリやファイルが作成されています。
~/.config ~/.local ~/bin /var/run/user/1000/docker /var/run/user/1000/docker.pid /var/run/user/1000/docker.sock /var/run/user/1000/runc※1000の部分は、UIDです。環境によって変わります。
※/var/run/user/1000/に追加されたディレクトリやファイルは、OS再起動時に消えますが、rootless dockerd が起動すると作成されます。パスの確認
homeディレクトリ下にインストールされています。
$ which docker ~/bin/dockerバージョンの確認
Versionの表記が少し気になりますが、Engine Versionも表示されています。
通常のDockerだと、Engine Versionは「dial unix /var/run/docker.sock: connect: permission denied」となって表示されません。$ docker version Client: Docker Engine - Community Version: master-dockerproject-2019-12-11 API version: 1.41 Go version: go1.12.12 Git commit: 08eaead2 Built: Wed Dec 11 23:52:32 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: master-dockerproject-2019-12-11 API version: 1.41 (minimum version 1.12) Go version: go1.13.4 Git commit: 1347481 Built: Wed Dec 11 23:58:15 2019 OS/Arch: linux/amd64 Experimental: true containerd: Version: v1.3.2 GitCommit: ff48f57fc83a8c44cf4ad5d672424a98ba37ded6 runc: Version: 1.0.0-rc9 GitCommit: d736ef14f0288d6993a1845745d6756cfc9ddd5a docker-init: Version: 0.18.0 GitCommit: fec3683プロセスを確認
4つのプロセスが動作しています。
全てのプロセスがユーザ権限で動作しています。$ ps xo user,cmd | grep docker user01 rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs user01 /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs user01 dockerd --experimental --storage-driver=vfs user01 containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level infoユーザ毎の違いを比べてみる
user02でRootless Dockerをインストールする前にバージョン確認してみました。
当然、見つかりません。[user02@localhost ~]$ docker version -bash: docker: コマンドが見つかりませんuser02でも、同様にRootless Dockerをインストールして、確認を続けます。
パスの確認
各ユーザのhomeの下を参照しているので、別ファイルです。
[user01@localhost ~]$ which docker ~/bin/docker [user02@localhost ~]$ which docker ~/bin/dockeri-node番号を確認すると、別ファイルだとわかります。
[user01@localhost ~]$ ls -i ~/bin/docker 33554563 /home/user01/bin/docker [user02@localhost ~]$ ls -i ~/bin/docker 33554616 /home/user02/bin/dockerバージョンの確認
実行ファイルは別々でも、同じバージョンなので違いがわからないです。
$ docker version Client: Docker Engine - Community Version: master-dockerproject-2019-12-11 API version: 1.41 Go version: go1.12.12 Git commit: 08eaead2 Built: Wed Dec 11 23:52:32 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: master-dockerproject-2019-12-11 API version: 1.41 (minimum version 1.12) Go version: go1.13.4 Git commit: 1347481 Built: Wed Dec 11 23:58:15 2019 OS/Arch: linux/amd64 Experimental: true containerd: Version: v1.3.2 GitCommit: ff48f57fc83a8c44cf4ad5d672424a98ba37ded6 runc: Version: 1.0.0-rc9 GitCommit: d736ef14f0288d6993a1845745d6756cfc9ddd5a docker-init: Version: 0.18.0 GitCommit: fec3683 [user02@localhost ~]$ docker version Client: Docker Engine - Community Version: master-dockerproject-2019-12-11 API version: 1.41 Go version: go1.12.12 Git commit: 08eaead2 Built: Wed Dec 11 23:52:32 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: master-dockerproject-2019-12-11 API version: 1.41 (minimum version 1.12) Go version: go1.13.4 Git commit: 1347481 Built: Wed Dec 11 23:58:15 2019 OS/Arch: linux/amd64 Experimental: true containerd: Version: v1.3.2 GitCommit: ff48f57fc83a8c44cf4ad5d672424a98ba37ded6 runc: Version: 1.0.0-rc9 GitCommit: d736ef14f0288d6993a1845745d6756cfc9ddd5a docker-init: Version: 0.18.0 GitCommit: fec3683プロセスを確認
4つのプロセスで、PIDが違っています。
[user01@localhost ~]$ ps xo pid,user,cmd | grep docker 1225 user01 rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs 1233 user01 /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs 1272 user01 dockerd --experimental --storage-driver=vfs 2866 user01 containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level info [user02@localhost ~]$ ps xo pid,user,cmd | grep docker 8013 user02 rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user02/bin/dockerd-rootless.sh --experimental --storage-driver=vfs 8022 user02 /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user02/bin/dockerd-rootless.sh --experimental --storage-driver=vfs 8074 user02 dockerd --experimental --storage-driver=vfs 8091 user02 containerd --config /run/user/1001/docker/containerd/containerd.toml --log-level info/ver/run の下のファイルの確認
そもそもパスが違うので別物です。
[user01@localhost ~]$ ls -l /var/run/user/1000 合計 4 srw-rw-rw-. 1 user01 user01 0 12月 22 21:54 bus drwx-----T. 7 user01 user01 180 12月 22 21:58 docker -rw-r--r-T. 1 user01 user01 4 12月 22 21:58 docker.pid srw-rw---T. 1 user01 user01 0 12月 22 21:58 docker.sock drwx-----T. 2 user01 user01 40 12月 22 21:58 runc drwxr-xr-x. 2 user01 user01 80 12月 22 22:00 systemd [user02@localhost ~]$ ls -l /var/run/user/1001 合計 4 srw-rw-rw-. 1 user02 user02 0 12月 22 22:01 bus drwx-----T. 5 user02 user02 140 12月 22 22:05 docker -rw-r--r-T. 1 user02 user02 4 12月 22 22:05 docker.pid srw-rw---T. 1 user02 user02 0 12月 22 22:05 docker.sock drwx-----T. 2 user02 user02 40 12月 22 22:05 runc drwxr-xr-x. 2 user02 user02 80 12月 22 22:05 systemdDockerコマンドでの違い
コンテナの確認
hello-worldコンテナを起動したので、停止状態のコンテナが残っています。
CONTAINER ID が違っており別々のコンテナだとわかります。[user01@localhost ~]$ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a9fa819c358e hello-world "/hello" 9 minutes ago Exited (0) 9 minutes ago crazy_lederberg [user02@localhost ~]$ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 236dbc03fa53 hello-world "/hello" 13 seconds ago Exited (0) 12 seconds ago heuristic_hypatiaイメージの確認
IMAGE ID が違がって・・・ないです。同じです。
※先に結論だけ書いておきますが、IMAGE IDは同じですが、実体は別物です。[user01@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kB [user02@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kBuser01で、nginx イメージをpullします。
[user01@localhost ~]$ docker image pull nginx Using default tag: latest latest: Pulling from library/nginx 000eee12ec04: Pull complete eb22865337de: Pull complete bee5d581ef8b: Pull complete Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566 Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latestダウンロードが終わるとイメージ一覧に追加されています。
[user01@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 231d40e811cd 4 weeks ago 126MB hello-world latest fce289e99eb9 11 months ago 1.84kBuser02は、ダウンロードしていないので、nginxイメージを確認できません。
[user02@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kBuser02でnginxコンテナを起動すると、「Unable to find image」と表示されダウンロードが開始しました。
[user02@localhost ~]$ docker container run nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx 000eee12ec04: Pull complete eb22865337de: Pull complete bee5d581ef8b: Pull complete Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566 Status: Downloaded newer image for nginx:latest ^C <-- nginxコンテナを停止 [user02@localhost ~]$nginxイメージのIMAGE IDも、user01、user02ともに同じになっています。
[user01@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 231d40e811cd 4 weeks ago 126MB hello-world latest fce289e99eb9 11 months ago 1.84kB [user02@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 231d40e811cd 4 weeks ago 126MB hello-world latest fce289e99eb9 11 months ago 1.84kBイメージは、各ユーザでダウンロードしないとダメなようです。
一見、IMAGE IDが同じなので、同じイメージを見ているように勘違いしそうです。イメージの実体を確認
hello-worldイメージを少し追ってみました。
jsonファイルの処理のために、pythonをインストールしています。$ sudo dnf install python3管理しているファイルを確認しました。
[user01@localhost ~]$ cat /home/user01/.local/share/docker/image/vfs/repositories.json | python3 -m json.tool { "Repositories": { "hello-world": { "hello-world:latest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e", "hello-world@sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e" }, "nginx": { "nginx:latest": "sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145", "nginx@sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566": "sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145" } } }このファイルは各ユーザのhomeディレクトリ下にあります。
パスが違うので当然、別物です。
また下記のように、i-node番号も違っています。(桁が違いすぎる)[user01@localhost ~]$ ls -li /home/user01/.local/share/docker/image/vfs/repositories.json 152 -rw-------. 1 user01 user01 542 12月 22 22:12 /home/user01/.local/share/docker/image/vfs/repositories.json [user02@localhost ~]$ ls -li /home/user02/.local/share/docker/image/vfs/repositories.json 100663444 -rw-------. 1 user02 user02 542 12月 22 22:21 /home/user02/.local/share/docker/image/vfs/repositories.jsonUUID(で合ってる?)をたどっていくと最終的に、helloコマンドにたどり着きます。
[user01@localhost ~]$ ls -li /home/user01/.local/share/docker/vfs/dir/e1b5ab5b419c6229106017654150711e2717ae81f3c8002bedb936f0f2786b4b/hello 33554585 -rwxrwxr-x. 1 user01 user01 1840 1月 1 2019 /home/user01/.local/share/docker/vfs/dir/e1b5ab5b419c6229106017654150711e2717ae81f3c8002bedb936f0f2786b4b/hello同じように、user02でも確認します。
「dir」の下のディレクトリ名がuser01と違っています。[user02@localhost ~]$ ls -li /home/user02/.local/share/docker/vfs/dir/f56bbc69cac68b78ef386f01643acbdaa89929e4f8f57bd2e8c8039e5dfa17c6/hello 67215817 -rwxrwxr-x. 1 user02 user02 1840 1月 1 2019 /home/user02/.local/share/docker/vfs/dir/f56bbc69cac68b78ef386f01643acbdaa89929e4f8f57bd2e8c8039e5dfa17c6/hellohelloコマンドのi-node番号が違っています。
dockerコマンド上は、同じIDに見えますが、イメージは別物だと言えます。イメージの削除
user01、user02ともに、停止コンテナを削除した後、user02でイメージの削除を行います。
[user02@localhost ~]$ docker image rm hello-world Untagged: hello-world:latest Untagged: hello-world@sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064 Deleted: sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e Deleted: sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3 [user02@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 231d40e811cd 3 weeks ago 126MBuser02では、hello-worldイメージが消えています。
user01では、残っています。[user01@localhost ~]$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 231d40e811cd 3 weeks ago 126MB hello-world latest fce289e99eb9 11 months ago 1.84kBdocker-compose の場合
docker-commposeを実行したときの動作を確認してみました。
$ sudo pip3 install docker-compose $ docker-compose version docker-compose version 1.25.0, build b42d419 docker-py version: 4.1.0 CPython version: 3.6.8 OpenSSL version: OpenSSL 1.1.1 FIPS 11 Sep 2018簡単なdocker-compose.ymlを作成します。
$ vi docker-compose.yml version: '3' services: wordpress: image: wordpress container_name: wordpress restart: always ports: - 80:80 environment: WORDPRESS_DB_PASSWORD: wp-password mysql: image: mysql:5.7 container_name: mysql restart: always environment: MYSQL_ROOT_PASSWORD: mysql-password実行します。
wordpressとmysqlのコンテナのダウンロードが始まるため、時間がかかります。$ docker-compose up -d Starting wordpress ... done Starting mysql ... done $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 32a9bd917995 mysql:5.7 "docker-entrypoint.s…" 2 minutes ago Up 33 seconds 3306/tcp, 33060/tcp mysql cfdd93b5f5ff wordpress "docker-entrypoint.s…" 2 minutes ago Up 33 seconds 0.0.0.0:80->80/tcp wordpressdocker-composeからの起動もできました。
確認できたので、終了させておきます。$ docker-compose down Stopping mysql ... done Stopping wordpress ... done Removing mysql ... done Removing wordpress ... done Removing network user01_default作業中に解決したエラー等
バージョン確認時のメッセージ
バージョン確認時等に、下記のようなメッセージが表示される。
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?環境変数の「DOCKER_HOST」が正しく設定されているか確認してください。
systemctlのメッセージ
systemctl --user status docker コマンドを実行した時に下記のようなエラーが出る。
level=warning msg="Running modprobe bridge br_netfilter failed with message: modprobe: ERROR: could not insert 'br_netfilter': Operation not permitted\ninsmod /lib/modules/4.18.0-80.el8.x86_64/kernel/net/bridge/br_netfilter.ko.xz \n, error: exit status 1"原因は、br_netfilterモジュールがロードされていないこと。
なので、OS起動時にロードするようにします。$ lsmod | grep br_netfilter br_netfilter 24576 0 bridge 188416 1 br_netfilter $ echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf最後に
ユーザごとに別の環境でDockerを使えるようになります。
当然、本来のDocker(/usr/bin/docker)と、別なものになります。
イメージが別になるなど注意が必要そうです。
気になるところはありますが、root権限が不要なるメリットがあるのでしばらく使ってみようと思います。
- 投稿日:2019-12-22T22:34:32+09:00
mong続報 - Dockerの名前生成ツールは2年前にPythonに移植されていた-
mong - Dockerのコンテナ名をランダム生成するコードをPythonに移植してみた -の続きです.
TL;DR
- namesgenerator というパッケージが見つかりました.mongは見事に車輪の再発明でした.
- 一方
namesgenerator
は2017年12月からアップデートされていないので辞書が古く,生成できるパターンが本家やmong
より少ないです.mong
でインスタンス生成が必須なのは使いにくかったのでnamesgenerator
を見習ってmong.get_random_name()
を追加してみましたnamesgenerator
Google, pypiで検索していて Dockerの名前生成ツールのPython版はないなぁと思っていました.しかしGitHubのコード検索をしてみたら,一瞬で下記が見つかりました.GitHubすごい.
https://github.com/shamrin/namesgenerator
上記のレポジトリにはPython版のほか,JavaScript/TypeScript版も含まれています.
pypiにも登録してあったので,インストールも一瞬です.
pypiの統計を見ると,月間1,000ダウンロードあるので,それなりに需要もあるようです.もう,これを使えばいいじゃん,と思ったのですが,反省も兼ねてコードリーディングしてみました
namesgenerator と mong の違い
大きく下記の4点が違います
1. 辞書の持ち方
2. 関数 or クラス
3.boring_wozniak
のときの再生成の方法
4.get_random_name
の引数辞書の持ち方
mong
は辞書を別ファイルにしているのに対して,namesgenerator
はpythonコード中にべた書きしています.べた書きすると,データ読み込み処理が不要で楽なのですが,メンテがつらそうです.
namesgenerator
は2年前に更新がとまっているので,それから本家に追加された語もあるかと思い,辞書のエントリ数で比べてみました:
-namesgenerator
の辞書は形容詞が93語で人名が160語
-mong
の辞書(現在のmobyと同じはず)は形容詞が108語, 人名が235語
mong
の方が形容詞で15語,人名で75語多いです.追加された語を調べる際に,べた書き方式だとGoのコードとPythonのコードを比較する必要があります.単純なdiffはできません.一方で
mong
ではmong/create_dict.py
を実行してGoコードからJSON形式の辞書を抜き出します.1行1単語にしてあるので,diffで差分が確認できます.もっとも,Goコードのフォーマットが大きく変わると辞書を抜き出すコードの修正が必要になるわけですが,mobyはそれなりに枯れているっぽいので大丈夫だと思います(思いたい).以上から,メンテは
mong
の方が楽かもしれません.関数 or クラス
namesgenerator
は名前生成を関数get_random_name
で提供していて,mong
はNameGenerator
クラスのget_random_name
で提供しています.クラスだといったんインスタンスを生成しないといけないので面倒です.mong
を作り始めたときに,辞書を変更可能に,とか乱数シードの管理を別にできた方がよいのでは,などと考えてクラスにしましたが,一晩頭を冷やしてみるとそんなことはありませんでした.namesgenerator
の方式が良いです.
boring_wozniak
のときの再生成の方法
namesgenerator
はwhile
ループで生成ロジックを回している一方でmong
はget_random_name
メソッドを再帰呼び出しています.boring_wozniak
が生成される確率は1/25,380なので,どちらでもいいかなと思います.
get_random_name
の引数
namesgenerator
はretry
関係のロジックがありません.ここ2年で追加されたのでしょうか.また,その代わりに形容詞と名詞をつなぐsep
を変更できます.これも仕様変更でしょうか?mongへの反映と今後の方針
いやー,見事に車輪の再発明をしてしまいました.
ただ,
namesgenerator
の実装と比較することで良い点,悪い点の答え合わせができましたので,経験値が溜まってよかったかなと思います.また,良いところは見習おう,ということで関数方式を取り入れてmong.get_random_name
を追加しました.つまり,今は下記のように書けるようになりました.
import mong mong.get_random_name() # 'trusting_engelbart' mong.get_random_name() # 'thirsty_brahmagupta'今後ですが,
namesgenerator
はメンテされていないので,mong
をメンテしていくとそれなりに需要あるのかもと思います.参考文献
- 投稿日:2019-12-22T21:53:26+09:00
【Laravel】migrateができないのは環境設定が間違っているからかも!あるあるエラーを.envと共に振り返る。
どうも、たかふみです。
Laravelで開発を行っていると、必ず使うであろう
php artisan migrate
コマンド。僕も何度もお世話になったコマンドです。今回は
php artisan migrate
を実行したときに出会ったエラーと共に解決策を書きたいと思います。あるあるエラー1:Connection refused
エラーメッセージ
Illuminate\Database\QueryException : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = sample and table_name = migrations and table_type = 'BASE TABLE') at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:665 661| // If an exception occurs when attempting to run a query, we'll format the error 662| // message to include the bindings with SQL, which will make this exception a 663| // lot more helpful to the developer instead of just the database's errors. 664| catch (Exception $e) { > 665| throw new QueryException( 666| $query, $this->prepareBindings($bindings), $e 667| ); 668| } 669| Exception trace: 1 PDOException::("SQLSTATE[HY000] [2002] Connection refused") /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 2 PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=sample", "root", "password", []) /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 Please use the argument -v to see more details. Whoops\Exception\ErrorException : Module 'zip' already loaded at Unknown:0 1| Exception trace: 1 {main}() /var/www/html/artisan:0解決策:DB_HOSTの設定を見直す。
.envファイルにあるDB_HOSTの値を確認したところ、
DB_HOST=127.0.0.1
となっていました。調べると、dockerの場合はコンテナ名に設定する必要があるとのことです。【修正後】 DB_HOST=mysqlこれで解決しました。
エラー2:Connection refused
Illuminate\Database\QueryException : SQLSTATE[HY000] [1049] Unknown database 'sample' (SQL: select * from information_schema.tables where table_schema = sample and table_name = migrations and table_type = 'BASE TABLE') at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:665 661| // If an exception occurs when attempting to run a query, we'll format the error 662| // message to include the bindings with SQL, which will make this exception a 663| // lot more helpful to the developer instead of just the database's errors. 664| catch (Exception $e) { > 665| throw new QueryException( 666| $query, $this->prepareBindings($bindings), $e 667| ); 668| } 669| Exception trace: 1 PDOException::("SQLSTATE[HY000] [1049] Unknown database 'sample'") /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 2 PDO::__construct("mysql:host=mysql;port=3306;dbname=sample", "root", "password", []) /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 Please use the argument -v to see more details.解決策:DB_DATABASEの値を確認
エラーメッセージから「'sample'というDBは存在しません。」ということが分かります。
この場合は、接続しようとしているDBの名前を見直す必要があります。僕の場合はDB「bookmark」に接続をしたかったので、.envファイルの該当箇所を下記のように修正しました。
【修正後】 DB_DATABASE=bookmarkこれで接続できました。
エラー3:修正しても接続できない。
解決策:envファイルが複数存在していないか確認
このエラー(?)が起きたときに一番ハマりました。笑
修正してもキャッシュを削除してもエラーが起きるため、もう一度ファイルを確認しようとファイル名で検索したところ、「.env」が2つあることに気づきました。不要なenvファイルを削除したら上手くいったのでこれが原因だったようです。
migrateに成功すれば晴れて下記の状態になるはずです。
このババババッと実行される感じが気持ちいいんですよね。root@592de04c5bde:/var/www/html/web# php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.03 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (0.01 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (0.01 seconds)まとめ:エラーメッセージを読んでenvファイルを見直そう。
こうするとmigrateが上手くいかないのは.envファイルの値、つまり環境設定が間違っていることが多かったように感じます。DBが無いと開発に着手できないので、ここはスムーズに乗り越えたいですね。それでは!
- 投稿日:2019-12-22T21:53:26+09:00
【Laravel】migrateができないのは環境変数が間違っているからかも!あるあるエラーを.envと共に振り返る。
どうも、たかふみです。
Laravelで開発を行っていると、必ず使うであろう
php artisan migrate
コマンド。僕も何度もお世話になったコマンドです。今回は
php artisan migrate
を実行したときに出会ったエラーと共に解決策を書きたいと思います。あるあるエラー1:Connection refused(Dockerの場合)
エラーメッセージ
Illuminate\Database\QueryException : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = sample and table_name = migrations and table_type = 'BASE TABLE') at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:665 661| // If an exception occurs when attempting to run a query, we'll format the error 662| // message to include the bindings with SQL, which will make this exception a 663| // lot more helpful to the developer instead of just the database's errors. 664| catch (Exception $e) { > 665| throw new QueryException( 666| $query, $this->prepareBindings($bindings), $e 667| ); 668| } 669| Exception trace: 1 PDOException::("SQLSTATE[HY000] [2002] Connection refused") /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 2 PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=sample", "root", "password", []) /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 Please use the argument -v to see more details. Whoops\Exception\ErrorException : Module 'zip' already loaded at Unknown:0 1| Exception trace: 1 {main}() /var/www/html/artisan:0解決策:DB_HOSTの設定を見直す。
.envファイルにあるDB_HOSTの値を確認したところ、
DB_HOST=127.0.0.1
となっていました。調べると、dockerの場合はDBのコンテナ名に設定する必要があるとのことです。【修正後】 DB_HOST=mysqlこれで解決しました。
エラー2:Connection refused
Illuminate\Database\QueryException : SQLSTATE[HY000] [1049] Unknown database 'sample' (SQL: select * from information_schema.tables where table_schema = sample and table_name = migrations and table_type = 'BASE TABLE') at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:665 661| // If an exception occurs when attempting to run a query, we'll format the error 662| // message to include the bindings with SQL, which will make this exception a 663| // lot more helpful to the developer instead of just the database's errors. 664| catch (Exception $e) { > 665| throw new QueryException( 666| $query, $this->prepareBindings($bindings), $e 667| ); 668| } 669| Exception trace: 1 PDOException::("SQLSTATE[HY000] [1049] Unknown database 'sample'") /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 2 PDO::__construct("mysql:host=mysql;port=3306;dbname=sample", "root", "password", []) /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 Please use the argument -v to see more details.解決策:DB_DATABASEの値を確認
エラーメッセージから「'sample'というDBは存在しません。」ということが分かります。
この場合は、接続しようとしているDBの名前を見直す必要があります。僕の場合はDB「bookmark」に接続をしたかったので、.envファイルの該当箇所を下記のように修正しました。
【修正後】 DB_DATABASE=bookmarkこれで接続できました。
エラー3:修正しても接続できない。
解決策:envファイルが複数存在していないか確認
このエラー(?)が起きたときに一番ハマりました。笑 修正してもキャッシュを削除してもエラーが起きるため、もう一度ファイルを確認しようとファイル名で検索したところ、「.env」が2つあることに気づきました。
不要なenvファイルを削除したら上手くいったのでこれが原因だったようです。
migrateに成功すれば晴れて下記の状態になるはずです。このババババッと実行される感じが気持ちいいんですよね。
root@592de04c5bde:/var/www/html/web# php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.03 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (0.01 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (0.01 seconds)まとめ:エラーメッセージを読んでenvファイルを見直そう。
こうするとmigrateが上手くいかないのは.envファイルの値、つまり環境変数が間違っていることが多かったように感じます。DBが無いと開発に着手できないので、ここはスムーズに乗り越えたいですね。それでは!
- 投稿日:2019-12-22T21:30:45+09:00
docker pullをちょっとだけ楽にするCLIつくりました
つくったもの
dockerのimageからtagを検索&選択してpullできるCLI
Goでつくった人生初のCLIツール
— kohbis (@kohbis) December 22, 2019
(docker pull をちょっとだけ楽にする) pic.twitter.com/F7qA86Inmp※動画の圧縮になれておらず、、画質が悪いです。。
バージョン等
- Go 1.13.5
- spf13/cobra ... CLIアプリケーションのライブラリ。
- manifoldco/promptui ... 対話型プロンプトのライブラリ。
- macOS Catalina 10.15.1
- Docker version 19.03.5
Goの経験は、以前ポストしたLチカセブンくらいでほぼ触ったことがありません。
「shellなら楽じゃん」と言われそうですが、今回は Dockerのアドベントカレンダー 。
必然的にDockerの開発言語であるGo言語を使うしかありません!CLIのライブラリは、urfave/cliも情報がたくさんありました。
しかし、今回は Dockerのアドベントカレンダー 。
必然的にDockerに採用されているcobraを使うしかありません!ソース
https://github.com/kohbis/dimg
※ 201919/12/22時点では、公式イメージ(library)だけ対応しています。
ポイント
タグ一覧取得
curlコマンドだと、下記でタグ一覧が取得できます。
(後述の公式ドキュメント参照)curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags/ | jq -r '.results|.[]|.name'
返ってくるJSONを、Goの構造体で表すとこうなります。
JSON->Structには、JSON-to-Goというサイトが、とても便利でした。type Tags struct { Count int `json:"count"` Next string `json:"next"` Previous interface{} `json:"previous"` Results []struct { Name string `json:"name"` FullSize int `json:"full_size"` Images []struct { Size int `json:"size"` Digest string `json:"digest"` Architecture string `json:"architecture"` Os string `json:"os"` OsVersion interface{} `json:"os_version"` OsFeatures string `json:"os_features"` Variant interface{} `json:"variant"` Features string `json:"features"` } `json:"images"` ID int `json:"id"` Repository int `json:"repository"` Creator int `json:"creator"` LastUpdater int `json:"last_updater"` LastUpdaterUsername string `json:"last_updater_username"` ImageID interface{} `json:"image_id"` V2 bool `json:"v2"` LastUpdated time.Time `json:"last_updated"` } `json:"results"` }このままだと1ページあたり10件しか取得できないため
Next
に次ページのURLがnullにならない限りループして、、、という面倒くさいことになります。
そのため、最初からクエリパラメータ?page_size=10000
をつけて取得しています。
(そんなにタグ数があるイメージってあるのかしら)タグ検索
promptuiは自前のsearcherを実装することができます。(感動)
これにより、マイナーバージョンが多い言語の実行環境イメージも見つけやすくなっています。$ go run main.go Image Name: ruby Searching "ruby" tags... "ruby" has 615 tags. Search: 2.6.█ ? Select Tag: ▸ 2.6.5-stretch 2.6.5-slim-stretch 2.6.5-slim-buster 2.6.5-slim ↓ 2.6.5-bustercobraも、promptuiも色々できることがありそうなので、今後拡張していきたいです。
まとめ
Docker アドベントカレンダー担当日の1日前に思いたったのですが、ライブラリが非常に強力で1日かからずにものをつくることができました!
今回作ったものは足りないところがたくさんありますが、実際つくってみると個人的には欲しい機能がどんどん思いついていきたので、今後継続的に開発していきたいと思います。
実は今年最初やりたいことのひとつに「CLIツールをつくる」がありまして、ぎりぎり達成することができたのでよかったです!!
(この記事を書いている最終に思い出しました笑)参考
Examples using the Docker Engine SDKs and Docker API
How do I authenticate with the V2 API?
- 投稿日:2019-12-22T21:18:35+09:00
Visual Studio Code - Remote Development を使用して PHP Xdebug を使ったデバッグを行う
要約
- VS Code の Remote Development で Xdebug する時は、
remote_host
の向き先を ローカルホストにする。- Remote Development の接続先の VS Code に PHP Debug を導入する。
.vscode/launch.json
で Xdebug からの情報を Listen する。はじめに
VS Code の 拡張機能 「 Remote Development 」は、これまでもどかしく感じていたローカル開発環境の構成を改善してくれそうで色々と試しています。
前回の記事 では、Vagrant で構築した仮想環境の上にある Docker コンテナに対して、 Remote Development で接続する方法を記載しました。
今回は、前回構築した環境に追加する形で、 PHP の Xdebug を用いた開発が出来るように設定方法をまとめてみます。
概要
書くこと
- Docker コンテナの起動時の Xdebug の設定方法
- VS Code Server で Xdebug のデータを Listen する方法
書かないこと
- Vagrant で構築する仮想環境に Docker や docker-compose を導入する方法。
想定環境
この記事で実現するシステムの構成は下図の通りです。
Remote Development の有無による違い
Remote Development を使用しない場合
Remote Development を使用せず、 Xdebug のデータを HostOS 上の VS Code で Listen する場合は以下のような構成でした。
この構成を実現するためには、 Xdebug の設定ファイルで remote_host の IP アドレスを指定する必要がありました。
Remote Development を使用する場合
Remote Development を使用する場合の構成は以下の構成になります。
Xdebug の remote_host は localhost を指定すればよく、送信先の IP をいちいち気にする必要がありません。
設定手順
0. 基本設的な設定手順
この記事で実現している構成の基本的な要素は以下の記事で解説しています。
1. XDebug が有効化された Docker コンテナの構築
Docker コンテナの構築に必要なリソースは以下の通りです。
command(bash@GuestOS)## Dockerfile ## authorized_keys : 公開鍵認証に使用する公開鍵。Dockerコンテナ内に配置する。 ## php/20-xdebug.ini : Xdebug の設定ファイル。Dockerコンテナ内に配置する。 $ tree . ├── Dockerfile ├── authorized_keys └── php └── 20-xdebug.ini
Dockerfile
は次のように記載します。Dockerfile# image FROM php:7.0.15-fpm ENV LANG C.UTF-8 RUN apt-get update -qq && \ apt-get install -y \ zlib1g-dev \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ && docker-php-ext-install zip \ && yes "" | pecl install xdebug \ && docker-php-ext-enable xdebug \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j$(nproc) gd RUN apt-get update \ && apt-get install -y libpq-dev \ && docker-php-ext-install pdo_mysql pdo_pgsql RUN apt-get update \ && apt-get install -my wget gnupg WORKDIR /opt/ # ここから最後までがポイント RUN apt-get update && apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile # 手元の公開鍵をコピー COPY authorized_keys /root/authorized_keys EXPOSE 22 # 公開鍵を使えるようにする (パーミッション変更など) CMD mkdir ~/.ssh && \ mv ~/authorized_keys ~/.ssh/authorized_keys && \ chmod 0600 ~/.ssh/authorized_keys && \ # 最後に ssh を起動 /usr/sbin/sshd -D # Xdebug の設定ファイルを Docker コンテナに配置 COPY php/20-xdebug.ini /usr/local/etc/php/conf.d/20-xdebug.iniXDebug の設定を以下のようにします。
php/20-xdebug.inixdebug.remote_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_connect_back = 1 xdebug.remote_host= "localhost" xdebug.remote_port = 9001 xdebug.remote_log = /var/log/xdebug.logDocker コンテナを以下のコマンドで構築します。
command(bash@GuestOS)$ pwd /vagrant/docker-sample $ tree . ├── Dockerfile ├── authorized_keys └── php └── 20-xdebug.ini # build $ docker build ./ -t example # Docker Container を起動 # Port:10000 を Port:22 に転送 $ docker run -d -p 10000:22 example # Docker Container に SSH 接続 $ ssh root@127.0.0.1 -p 10000 -i ~/.ssh/private_key2.
.ssh/config
の設定Host OS から 仮想環境(GuestOS、Dockerコンテナ)に SSH接続するために、以下の設定を行います。
.ssh/config(Windows10)# Vagrant で起動した仮想環境への SSH接続設定 Host vagrant-os HostName 127.0.0.1 User vagrant Port 2222 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key IdentitiesOnly yes LogLevel FATAL # Docker で起動した仮想環境への SSH接続設定 Host docker-os Hostname 127.0.0.1 User root Port 10000 ProxyCommand C:\Windows\System32\OpenSSH\ssh.exe -W %h:%p vagrant-os IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key3. Visual Studio Code の設定
1.) と 2.) の設定をすることで、 VS Code の Remote Development の SSH Target には以下のように接続先候補が表示されるようになります。
上図のうち、
docker-os
の方にアクセスした際の画面が下図です。
ここで、.vscode/launch.json
を作成して、 Xdebug からのデータを Listen 出来るよう設定しています。
.vscode/launch.json
の設定は以下の通りです。
php/20-xdebug.ini
のxdebug.remote_port
で指定した Port と番号を合わせて設定します。
Docker コンテナ上の VSCode に PHPDebug 機能拡張 をインストールすることも忘れず行います。.vscode/launch.json{ // IntelliSense を使用して利用可能な属性を学べます。 // 既存の属性の説明をホバーして表示します。 // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9001 }, { "name": "Launch currently open script", "type": "php", "request": "launch", "program": "${file}", "cwd": "${fileDirname}", "port": 9001 } ] }4. 動作確認
3.) までの手順で設定した内容が正しく設定されているか確認してみます。
Docker コンテナ内で適当な PHP プログラムを作成します。test.php<?php echo "test";PHPDebug で Listen しながら、適当な箇所でブレイクポイントを設定して テストプログラムを実行します。
command(bash@DockerContainer)$ pwd /root $ ls test.php # テストプログラムの実行 $ php test.php test実行結果は以下のようになりました。
おわりに
書いたあとで見返してみると、なんのことは無い、普通の Xdebug の設定でした。
リモートの仮想環境に VS Code Server があり、そこと HostOS の VSCode がやり取りすることによって、リッチな VSCode の UI を使用して仮想環境上のリソースが編集出来るので、ひじょ~~~に便利です。
参考
今回の記事を作成するにあたって参考した記事です。
Remote Development
- 投稿日:2019-12-22T21:16:27+09:00
[備忘録]centos7.4にdockerのインストール
はじめに
centos7.4にdocker, docker-composeをインストールする。
pipを使用してインストールする。epel-releaseのインストール
$ sudo yum install -y epel-releasepythonとpipのインストール
すでにpythonのインストールがされている場合には不要。
$ sudo yum install -y python3docker, docker-composeのインストール
$ sudo pip3 install docker docker-composeuserをdockergroupに追加
$ sudo groupadd docker $ sudo usermod -aG docker ${user}dockerサービスの起動
$ sudo systemctl start docker動作確認
動作確認としてcentosのコンテナを起動してみる
$ sudo docker run centos
docker-composeで
docker-compose.ymlversion: "3" services: centos: build: . tty: yes volumes: - ./data:/dataDockerfileFROM centos:7 RUN yum update & \\ yum install -y epel-release yum install -y vim$ docker-compose build
- 投稿日:2019-12-22T19:07:49+09:00
CentOS 7にGitLab Runnerをインストールし、静的解析と単体テストを自動化する
CentOS 7にGitLab Runnerをインストールし、静的解析と単体テストを自動化する
Gitlab Runner とは?
GitLab CI / CD は、GitLab の一機能であり、GitLab のプロジェクトで管理している特定のブランチの更新やマージをトリガーとして、ビルドジョブやテストジョブを呼び出すことができます。
GitLab Runner は GitLab CI / CD 上から指示されたスクリプトを実行したり、一時的にDocker コンテナを生成してジョブを実行したりするプロセスです。
GitLab とは何ぞや?という方は、下記をご一読いただければと思います。
作成するに至った経緯
- GitLab Runner のインストール方法と設定をアウトプットとして残したかったため。
- GitLab Runner の動作確認をしたかったため。
対象読者
- CentOS 7 にGitLab Runner をインストールしたい方
- GitLab Runner を使用して、Python の静的解析( pylint )を自動化を試したい方
- GitLab Runner を使用して、Python の単体テスト( pytest )を自動化を試したい方
- ※ 今回Pythonを使用したのは、GitLab Runnerを試す敷居が低いと感じたためです。
前提条件
- GitLab はインストール済みであるとする
- Docker はインストール済みであるとする
- Webブラウザはインストール済みであるとする
- 今回は、proxy 環境下ではないものとする
GitLab をインストールされていない場合は、下記を参照してインストール願います。
構成図のイメージ
実行環境
# cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core)# gitlab-rake gitlab:env:info GitLab information Version: 12.5.2 Revision: 49482945d28 Directory: /opt/gitlab/embedded/service/gitlab-rails DB Adapter: PostgreSQL DB Version: 10.9 URL: http://gitlab.example.com HTTP Clone URL: http://gitlab.example.com/some-group/some-project.git SSH Clone URL: git@gitlab.example.com:some-group/some-project.git Using LDAP: no Using Omniauth: yes Omniauth Providers:GitLab Runner の導入
1.1. GitLab Runner の動作確認用のプロジェクトの登録
GitLab Runner インストールの前に、動作確認用のサンプルプロジェクトを下記のイメージのように登録しておきます。
test_sample.py の中身は下記の通りです。以下のサイトから流用しております。
pylint や pytest 実行時に怒られないようにコメントをつけたり、値を変更したりしております。
.gitlab-ci.yml については、後程触れますので、ここでは解説を割愛いたします。test_sample.py# content of test_sample.py """This is a test program.""" def func(var_x): """This is a test function.""" return var_x + 1 def test_answer(): """This is a test function.""" assert func(3) == 41.2. GitLab Runner のインストール
下記のコマンドを実行し、GitLab Runner をインストールします。
# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash # yum install -y gitlab-runner1.3. GitLab Runner の登録情報の確認
次にGitLab CI / CD への登録を行います。そのために登録情報が必要となります。
Webブラウザ上で、GitLab のプロジェクトの画面にて下記の順でクリックし、登録情報を表示します。[$プロジェクト名] ┗ [設定] ┗ [CI/CD] ┗ [Runner] ┗ [展開] ┗ [Set up a specific Runner manually]1.4. GitLab CI/CD への Runner 登録
下記のコマンドを実行し、GitLab CI/CD への Runner 登録を行います。
# gitlab-runner register
設定項目 入力内容 Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): Set up a specific Runner manually の 2. に表示されているURL を入力 Please enter the gitlab-ci token for this runner: Set up a specific Runner manually の 3. に表示されている登録トークン を入力 Please enter the gitlab-ci description for this runner: Runner に関する内容を入力 Please enter the gitlab-ci tags for this runner (comma separated): Runner に関するタグを入力(タグなしも可) Please enter the executor: Execcuter の種類を選択。今回は Docker Please enter the default Docker image (e.g. ruby:2.6): Dockerのコンテナに使用するイメージを選択。今回は centos:centos7 Runtime platform arch=amd64 os=linux pid=5622 revision=577f813d version=12.5.0 Running in system-mode. Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): http://localhost/ Please enter the gitlab-ci token for this runner: pjx_24-Pjmu42k1xfJyz Please enter the gitlab-ci description for this runner: [localhost.localdomain]: CI example Please enter the gitlab-ci tags for this runner (comma separated): Registering runner... succeeded runner=pjx_24-P Please enter the executor: docker, docker-ssh, shell, ssh, docker-ssh+machine, custom, parallels, virtualbox, docker+machine, kubernetes: docker Please enter the default Docker image (e.g. ruby:2.6): centos:centos7 Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!これにてGitLab CI/CD への Runner 登録が完了しました。
1.3. GitLab Runner の登録情報の確認で開いていたページを更新すると、このプロジェクトに紐づいたRunner が表示されます。Runner 横のRun untagged jobs にチェックが入っていることを確認します。
チェックが入っていない場合は、チェックを入れて [ 変更を保存 ] をクリックしておきましょう。1.5. config.toml の設定
GitLab のリポジトリへのアクセス時に、名前解決ができるように設定をします。
# vi /etc/gitlab-runner/config.tomlconcurrent = 1 check_interval = 0 [session_server] session_timeout = 1800 [[runners]] name = "CI example" url = "http://localhost/" token = "Ke1p4Hh96ZzohrcRS3nn" executor = "docker" [runners.custom_build_dir] [runners.docker] tls_verify = false image = "centos:centos7" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0 extra_hosts = ["gitlab.example.com:10.0.2.15"] ★ 設定箇所 [runners.cache] [runners.cache.s3] [runners.cache.gcs]1.6. GitLab Runnerの再起動
下記のコマンドを実行し、GitLab Runnerの再起動します。
# systemctl restart gitlab-runner2. GitLab CI / CD Jobs の設定
2.1. ジョブの定義
GitLab CI / CD におけるジョブは、.gitlab-ci.yml という設定ファイルに定義します。これをプロジェクトリポジトリのトップディレクトリに隠しファイル形式でコミットすることによって、動的にジョブが登録される仕組みになっています。書き方につきましては、下記を参考願います。
それでは、.gitlab-ci.yml に実行するジョブを下記のように定義し、コミットします。
gitlab-ci.ymlimage: centos:centos7 stages: - pylint - pytest pylint: stage: pylint script: - yum update -y - yum install -y https://centos7.iuscommunity.org/ius-release.rpm - yum install -y python36u python36u-libs python36u-pip python36u-devel - pip3.6 install --upgrade pip - pip install pylint - pylint test_sample.py pytest: stage: pytest script: - yum update -y - yum install -y https://centos7.iuscommunity.org/ius-release.rpm - yum install -y python36u python36u-libs python36u-pip python36u-devel - pip3.6 install --upgrade pip - pip install pytest - pytest test_sample.py3. GitLab Runner の動作確認
コミットが終わったら、Webブラウザ上でGitLab のプロジェクトの画面にて下記の順でクリックし、実行されているジョブのログを表示します。
[$プロジェクト名] ┗ [CI/CD] ┗ [ジョブ] ┗ [実行中].gitlab-ci.yml に記述した内容が上から順に実行されていきます。
下記のように、Job succeeded と表示されたら、pylint と pytest のジョブがそれぞれ正常に完了したことになります。ちなみに test_sample.py を元の値のままにすると、下記のようにJob failed となり、pytest のジョブが失敗します。
この失敗したことを、[ 新規課題 ] をクリックして、課題チケットとして発行することもできるようです。また、スケジュール設定をすることで、指定した日時にジョブを実行することも可能です。
他にも、.gitlab-ci.yml に ** artifacts** パラメータを指定することで、実行結果をファイルとして保存することも可能です。
これらの機能も活用して、楽していきたいですね。まとめ
CentOS 7にGitLab Runnerをインストールし、静的解析と単体テストを自動化することができました。
今回は、動作確認が容易なPythonで実行してみましたが、他の言語も自動化確認してみたいです。proxy が絡むと設定しなければいけない点が増えますので、環境設定が大変になりそうです。
Docker Hub にあるイメージでは、ジョブがこけてしまう場合は、自分でDockerfile 作成して、専用のイメージを作成しないといけないかもしれないです。個人的にJava with Maven なんかは特に大変そうです(´・ω・`)。自動化を進めていってできた時間をまた、別のことに費やして、どんどん楽していきたいですね。
参考URL
- Install GitLab Runner using the official GitLab repositories
- GitLab CI/CD Pipeline Configuration Reference
- GitLabのgitlab-runnerを使えるようにするまでの備忘録
- 【docs.pytest.org】Installation and Getting Started
- gitlab-ciでCouldn't resolve hostがでるとき
- CentOS7でdocker buildでyumが失敗する
参考書籍
- 投稿日:2019-12-22T18:09:31+09:00
Dockerで立ち上げたGitLabの定期バックアップ方法
はじめに
どうも!生産技術部のエンジニアです。GitLabのサーバを立ち上げて、最初に実施する事の一つがデータのバックアップだと思います。ここでは、GitLabのアプリケーションデータ及び設定ファイルを定期バックアップする方法を紹介します。
GitLabサーバを一から構築される方は、以下からご覧ください。
「proxy環境下でDocker Composeを用いてCentOS7上にGitLab Dockerを作成」環境
- CentOS : 7.6.1810
- Docker-CE : 19.03.1
- Docker Compose : 1.25.0
- GitLab-CE Docker : 12.2.0-ce.0
前提条件
Docker、GitLabの導入が実施済みであること。
「proxy環境下でDocker Composeを用いてCentOS7上にGitLab Dockerを作成」
を参考に導入してください。DockerでGitLabを起動した場合のバックアップ
GitLabのバックアップをする上で、以下の二種類のデータのバックアップが必要になります。
- アプリケーションデータ
- データベースやリポジトリのデータ
- 設定ファイル
gitlab.rb
やgitlab-secrets.json
などの設定ファイルGitLabにはアプリケーションデータ用のバックアップコマンドが用意されていますが、設定ファイルはバックアップされません。公式によると、データベースには二段階認証のための暗号化情報などが含まれますが、それらの情報とそのキーを同じ場所に置くことは、暗号化を行う目的に反しているとか謎、、、細かいことは抜きにして、まずはアプリケーションデータのバックアップ方法から説明します。Dockerでのバックアップ方法は、以下の様に実施します。
$ sudo docker exec -t <container name> gitlab-backup create
<container name>
は、以下のコマンドで確認します。$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dc2c32a5a3d6 gitlab/gitlab-ce:latest "/assets/wrapper" 8 days ago Up 8 days (healthy) 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8001->8001/tcp, 0.0.0.0:4022->22/tcp gitlab_gitlab_1実際に実行すると、この様なログが表示され、バックアップが出来上がります。
$ sudo docker exec -t gitlab_gitlab_1 gitlab-backup create <略> 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping uploads ... 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping builds ... 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping artifacts ... 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping pages ... 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping lfs objects ... 2019-12-03 08:22:14 +0000 -- done 2019-12-03 08:22:14 +0000 -- Dumping container registry images ... 2019-12-03 08:22:14 +0000 -- [DISABLED] Creating backup archive: 1575361334_2019_12_03_12.3.5_gitlab_backup.tar ... done Uploading backup archive to remote storage ... skipped Deleting tmp directories ... done done done done done done done done Deleting old backups ... done. (0 removed) Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data and are not included in this backup. You will need these files to restore a backup. Please back them up manually. Backup task is done.バックアップを確認するとこの様なアーカイブファイルが生成されます。
# ls -la /srv/gitlab/data/backups/ total 18344 drwx------. 2 libstoragemgmt root 4096 Dec 3 17:22 . drwxr-xr-x. 22 root root 4096 Nov 25 10:11 .. -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Dec 3 17:22 1575361334_2019_12_03_12.3.5_gitlab_backup.tarアプリケーションデータの定期バックアップ
設定ファイルには
backup_path
(バックアップファイルの保存先)、backup_archive_permissions
(ファイルパーミッション)、backup_keep_time
(キープタイム)を設定します。キープタイムを設定することで、古くなったファイルを自動的に削除してくれます。値の単位は秒ですので、1週間分残したい場合は$7(日間)\times24(時間)\times3600(秒) = 604800$となります。gitlab.rb### Backup Settings ###! Docs: https://docs.gitlab.com/omnibus/settings/backups.html gitlab_rails['manage_backup_path'] = true gitlab_rails['backup_path'] = "/var/opt/gitlab/backups" ###! Docs: https://docs.gitlab.com/ce/raketasks/backup_restore.html#backup-archive-permissions gitlab_rails['backup_archive_permissions'] = 0644 # gitlab_rails['backup_pg_schema'] = 'public' ###! The duration in seconds to keep backups before they are allowed to be deleted gitlab_rails['backup_keep_time'] = 604800crontabは定期スケジューリング用のコマンドです。管理者権限を持ったユーザで実施します。
$ sudo su - $ crontab -e上記コマンドを実行すると、エディタが起動しますので、実行したいコマンドを記述し保存します。
# 分 時 日 月 曜日 <実行コマンド> 0 2 * * * docker exec -t gitlab_gitlab_1 gitlab-backup create CRON=1 # <***必ず最後に改行を入れてください。***>正常に動作すると、1日1回(2:00)にバックアップが作成され、一週間が過ぎたバックアップファイルは削除されるようになります。
$ ls -la /srv/gitlab/data/backups/ total 16012 drwx------. 2 libstoragemgmt root 4096 Dec 3 02:00 . drwxr-xr-x. 22 root root 4096 Nov 25 10:11 .. -rw-r--r--. 1 libstoragemgmt polkitd 2222080 Nov 27 02:00 1574787621_2019_11_26_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2222080 Nov 28 02:00 1574874021_2019_11_27_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Nov 29 02:00 1574960421_2019_11_28_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Nov 30 02:00 1575046821_2019_11_29_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Dec 1 02:00 1575133220_2019_11_30_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Dec 2 02:00 1575219621_2019_12_01_12.3.5_gitlab_backup.tar -rw-r--r--. 1 libstoragemgmt polkitd 2385920 Dec 3 02:00 1575306021_2019_12_02_12.3.5_gitlab_backup.tar設定ファイルの定期バックアップ
backupsの中にconfigディレクトリを作成し、設定ファイルのバックアップを行います。バックアップの方法はtarコマンドを使います。バックアップファイルのファイル名はアプリケーションデータのバックアップファイル名に合わせるために、dateコマンドを利用します。
--date '1 day ago'
をつける事で1日前の日付を取得できます。1週間経過した設定ファイルのバックアップについても削除します。# lオプションでcrontabに設定したコマンドを表示 $ crontab -l # Creating backup archive 0 2 * * * docker exec -t gitlab_gitlab_1 gitlab-backup create CRON=1 # Create config backup archive 0 2 * * * tar cfz /srv/gitlab/data/backups/config/$(date --date '1 day ago' "+\%s_\%Y_\%m_\%d_12.3.5_gitlab_etc.tar.gz") -C /srv/gitlab config # Delete old config backups 0 2 * * * find /srv/gitlab/data/backups/config -mtime +6 | xargs rm -rfバックアップコマンドをスクリプトにまとめる
実行したコマンドをスクリプトにまとめます。注意が必要な点は、設定ファイルのバックアップファイル名に
\%s_\%Y_\%m_\%d
と記述しましたが、シェルスクリプトでは、バックスラッシュは不要です。gitlab_backup.sh#!/bin/sh BKDIR=/srv/gitlab/data/backups # Creating backup archive docker exec -t gitlab_gitlab_1 gitlab-backup create # Create config backup archive tar cfz $BKDIR/config/$(date --date '1 day ago' "+%s_%Y_%m_%d_12.3.5_gitlab_etc.tar.gz") -C /srv/gitlab config # Delete old config backups find $BKDIR/config -mtime +6 | xargs rm -rfcrontabはこの様になります。
$ crontab -l # Creating backup archive # 0 2 * * * docker exec -t gitlab_gitlab_1 gitlab-backup create CRON=1 # Create config backup archive # 0 2 * * * tar cfz /srv/gitlab/data/backups/$(date "+\%s_\%Y_\%m_\%d_12.3.5_gitlab_etc.tar.gz") -C /srv/gitlab config # Delete old config backups # 0 2 * * * find /srv/gitlab/data/backups/config -mtime +6 | xargs rm -rf 0 2 * * * <ファイルパス>/gitlab_backup.sh CRON=1最後に
お疲れ様です。これで定期バックアップが可能になり、安心したGitLabライフを満喫できます。
ご参考
- 投稿日:2019-12-22T16:37:25+09:00
Supervisor を Docker で入門する
supervisor を使ったことがないので理解したいと思います。
最終的には、以下の4つのファイルで、プロセスを二つ管理したいとおもいます。
$ tree . ├── Dockerfile ├── hello.sh ├── hey.sh └── supervisord.confまず適当に動かしてみる
ひとまず、supervisor を入れたコンテナを用意して、そのコンテナ内で supervisor の動きを確認してみます。
まず centos な環境で使いたかったので、Dockerfile を作成して、centos なイメージに pip いれて、それ経由で supervisor 入れます。
DockerfileFROM centos:7 # こっちでもいい # RUN yum install -y python-setuptools && \ # easy_install supervisor RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \ python get-pip.py && \ pip install supervisor次に、config ファイルを作りたいのですが。雛形を生成するコマンドがあるので、それを使ってみます。
コンテナの中に入って
echo_supervisord_conf
を打ちます。[root@4e1290d0e494 /]# echo_supervisord_conf ; Sample supervisor config file. ; ; For more information on the config file, please see: ; http://supervisord.org/configuration.html ; ; Notes: ; - Shell expansion ("~" or "$HOME") is not supported. Environment ; variables can be expanded using this syntax: "%(ENV_HOME)s". ...以下略...これをそのまま
supervisord.conf
というファイルにコピペしてしまいます。コメントアウトを消すと以下のような感じです。
supervisor.conf[unix_http_server] file=/tmp/supervisor.sock [supervisord] logfile=/tmp/supervisord.log logfile_maxbytes=50MB logfile_backups=10 loglevel=info pidfile=/tmp/supervisord.pid nodaemon=false minfds=1024 minprocs=200 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///tmp/supervisor.sockつぎに動かすプログラムを書きます、本当に少しです。
ひとまず、永久に動き続けるのを書いてみます。
hello.sh#!/bin/bash while :; do echo "Hello, world!" sleep 1 doneこれを supervisor 経由で動かすには、
supervisord.conf
に以下を追記します。[program:hello] command=/hello.sh stdout_logfile=/tmp/hello.logこれでコンテナ内に、
hello.sh
を/
に、supervisord.conf
を/etc
に配置して、コンテナ内で以下のコマンドを打つと動きます。supervisor -c /etc/supervisord.conf
Dockerfile の CMD で動かすようにする
DockerfileFROM centos:7 RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \ python get-pip.py && \ pip install supervisor COPY hello.sh /hello.sh COPY hey.sh /hey.sh COPY supervisord.conf /etc/supervisord.conf CMD supervisord -c /etc/supervisord.confhello.sh#!/bin/bash sleep 1 echo 'hello'hey.sh も実質 hello.sh と同じ。
supervisord.conf[unix_http_server] file=/tmp/supervisor.sock ; the path to the socket file [supervisord] logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 loglevel=info ; log level; default info; others: debug,warn,trace pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid nodaemon=true ; start in foreground if true; default false minfds=1024 ; min. avail startup file descriptors; default 1024 minprocs=200 ; min. avail process descriptors;default 200 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [program:hello] command=/hello.sh stdout_logfile=/tmp/hello.log autostart=true autorestart=true [program:hey] command=/hey.sh stdout_logfile=/tmp/hey.log autostart=true autorestart=true重要なポイントは
nodaemon=true
としている点です。これをしないとコンテナがすぐ終了しちゃいます。動作の確認には以下のような docker コマンドでやっています。
# ビルド $ docker build -t supervisor-image . # ラン $ docker run --name supervisor-container -d supervisor-image # これでコンテナ内へ $ docker exec -it supervisor-container bash # プロセスを見てみる [root@7cf591f95fbd /]# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 1.4 0.9 115748 19948 ? Ss 07:33 0:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisord.conf root 49 0.5 0.1 11836 2864 pts/0 Ss 07:33 0:00 bash root 86 0.0 0.1 11696 2540 ? S 07:33 0:00 /bin/bash /hello.sh root 87 0.0 0.1 11696 2668 ? S 07:33 0:00 /bin/bash /hey.sh root 88 0.0 0.0 4372 692 ? S 07:33 0:00 sleep 1 root 89 0.0 0.0 4372 688 ? S 07:33 0:00 sleep 1 root 90 0.0 0.1 51760 3516 pts/0 R+ 07:33 0:00 ps aux # ログを見てみる [root@7cf591f95fbd /]# tail -f /tmp/hey.log hey hey hey hey以上です。
- 投稿日:2019-12-22T15:16:40+09:00
Dockerのマルチステージビルドで、ビルド環境と実行環境のセットアップを1つのDockerfileで完結させよう
Code Chrysalis Advent Calendar 2019、24日目の投稿です。
こんにちは。現在、CodeChrysalisのイマーシブブートキャンプ(Cohort 10)に参加中のなおとです。
ブートキャンプも残すところ1週間を切りました。今は12/26(木)に行われるDemo Dayという卒業発表会に向けてチーム開発を日々頑張っています。お時間ある方は是非Demo Dayにご参加ください!
Demo Day の詳細はこちらになります。Code Chrysalisのブートキャンプには、多言語週間(Polyglottal Week)と呼ばれる、今まで使ったことのないプログラミング言語を1つ選択し、1週間で言語の習得からアプリケーション開発までを行うという機会があります。
私はこの多言語週間で、今まで扱ったことのなかったGolangを選択し、Golangを使ったフルスタックアプリケーションを作成しました。また、Dockerに興味があったので、Docker上でアプリをビルド・実行する方法も合わせて学びました。今回は、私が学んだ内容の一部についてご紹介したいと思います。
はじめに
本稿では、Dockerの基本的な知識があることを前提として、Dockerの重要な機能の1つであるマルチステージビルドについて解説していきます。
また、以下のレポジトリにサンプルコードを用意しました。今回説明する内容は、すべて以下のレポジトリをもとに検証を行っているので、ご自身の環境で試したい場合は合わせてご覧ください。
https://github.com/Imamachi-n/docker-multi-stage-build-101Docker上でアプリケーションをビルド・実行してみよう
まず始めに、作成したアプリケーションをDockerコンテナ上でビルド・実行したい場合、どのような方法を取ればいいのでしょうか?まずは簡単な例として、Golangで作成したアプリケーション(REST APIサーバ)を以下のDockerfileを用いて、ビルド・実行する場合を考えてみましょう。
以下に、サンプルのDockerfileを示しました。Dockerのベースイメージとして、公式の
golang
イメージを使っています。
ファイルの内容を簡単に説明すると、
1.ENV
でGolangのビルド条件(OS、CPUアーキテクチャ等)を環境変数として設定。
2.WORKDIR
で作業ディレクトリを指定。
3.COPY
で、ローカル環境にあるGolangプロジェクトをDockerコンテナ内にコピー。
4.RUN
でGolangのアプリケーションをビルドするコマンドを実行。
5.EXPOSE
で9000番のポートを開け、このポートを通してDockerコンテナと通信ができるように設定。
6. 最後に、ENTRYPOINT
でDockerコンテナ起動時に、Golangのアプリケーションが起動するように指定。
という内容が記述されています。FROM golang:1.13.4 ENV CGO_ENABLED=0 ENV GOOS=linux ENV GOARCH=amd64 WORKDIR /docker-multi-stage-build-101 COPY . . RUN go build -o "bin/goServer" EXPOSE 9000 ENTRYPOINT ["/docker-multi-stage-build-101/bin/goServer"]Dockerfileから、Dockerイメージをビルドし実行すると、Golangで作成したREST APIサーバが立ち上がります。
$ docker build . -f docker/01_raw/Dockerfile -t go-server-raw:dev $ docker run --rm -p 9000:9000 go-server-raw:dev [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /api/user/:name/*action --> docker-multi-stage-build-101/route.GetAction (4 handlers) [GIN-debug] GET /api/welcome --> docker-multi-stage-build-101/route.GetWelcome (4 handlers) [GIN-debug] Listening and serving HTTP on :9000 2019/12/22 02:31:38 Defaulting to port :9000続いて、以下の通りに、
curl
コマンドを使ってDockerコンテナで立ち上げたREST APIサーバにアクセスしてみましょう。Hello Code Chrysalis
と表示されれば成功です。これで、Dockerコンテナ上でアプリケーションが起動していることが確認できました。$ curl -X GET 'http://localhost:9000/api/welcome?firstname=Code&lastname=Chrysalis' Hello Code Chrysalis次に、
docker images
コマンドを使って、Dockerイメージのサイズを見てみましょう。なんと、915MBというかなり大きなサイズになっていることがわかります。$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE go-server-raw dev eb8f35015c94 11 seconds ago 915MB
これは、アプリケーションの実行に不要なもの(ビルド時に使用したライブラリ等)が、そのまま同じDockerイメージ内にゴミとして残ってしまっているためです。AWSやGCPなどの環境にデプロイすることを考えると、できればDockerイメージのサイズを小さくしたいですね。
この問題を解決するために、マルチステージビルドが登場する以前は、Builderパターンと呼ばれるコンテナ管理方法が利用されていました。
マルチステージビルド以前: Builderパターン
Builderパターンでは、ビルド用とデプロイ用の2つのDockerfileを用意します。ビルド用のDockerコンテナでアプリケーションをビルドし、アプリケーションの実行に必要なものだけを実行用のコンテナにコピーします。
結果として、Builderパターンでアプリケーションが動作するDockerコンテナを用意した場合、以下のものが必要となります。
- 2つのDockerfile(ビルド用と実行用)
- (ビルド環境から実行環境への)ビルド成果物の受け渡し用のシェルスクリプト
これにより、上述したDockerイメージの肥大化を防ぐことができます。ただ、ちょっと考えてみてください。これってめんどくさくないですか?あと、複数のファイルに設定情報が散らばってしまっています。作成するDockerイメージが増えていった場合、ソースコードの管理が複雑化していく気がします…。Dockerマルチステージビルド
そこで、満を持してマルチステージビルドの登場です。この機能は、Docker 17.05から追加された機能になります。端的に言うと、Builderパターンで実践していた内容を1つのDockerfileにまとめることができます。
ビルドステージ(中間コンテナイメージ)
普段ベースイメージを指定するために、Dockerfile内に
FROM
命令を記述していると思います。マルチステージビルドでは、以下のように、1つのDockerfile内にFROM
命令を複数記述します。
前半のFROM
以下のブロックのことをビルドステージ(中間Dockerイメージ)と呼んでいます。この中間Dockerイメージは、実行用Dockerイメージにビルド成果物を渡した後、削除されます。そのため、最終的に生成される実行用のDockerイメージに含まれることはありません。ビルドステージの命名
続いて、
AS
を使うことで、ビルドステージに対して名前を付けることができます。下図の例は、ビルドステージの中間Dockerイメージに対してbuilder
という名前を指定しています。
ちなみに名前を指定しなかった場合、
FROM
命令の順番に合わせて0, 1, 2,...
という連番名が自動で振られます。例えば、上図の場合だと、ビルドステージは0
、実行用のDockerイメージは1
という連番名が振られます。ビルド成果物をビルド環境から実行環境のDockerイメージへコピー
ビルドステージで作成したビルド成果物を、後半の
FROM
以下のブロック(アプリケーション実行用のDockerイメージ)にコピーすることができます。方法としては、COPY
の--from
オプションでビルドステージ名を指定し、ビルド成果物をビルドステージから実行用のDockerイメージにコピーします。
このように、マルチステージビルドを使うことで、ビルド用と実行用のDockerfileを分けることなく、1つのDockerfileで記述することができるようになります。実はそれ以外にもメリットがあります。ビルド用と実行用にDockerベースイメージをそれぞれ指定することができるので、例えば、
alpine
などの軽量コンテナイメージを実行用のDockerベースイメージとして選択することができます。こうすることで、作成されたDockerイメージ内に、アプリケーションの実行に必要なものだけを配置することができ、コンテナのサイズをよりスリム化させることができます。マルチステージビルドの具体例
それでは、具体的な例として、最初にお見せしたGolangのアプリケーション(REST APIサーバ)をマルチステージビルドを使ったDockerfileに書き換えてみたいと思います。
以下がDockerfileの内容になります。先程とほとんど変わりませんが、
COPY --from=builder
でビルドステージ内のビルド成果物(/docker-multi-stage-build-101/bin/goServer
)を、実行用のDockerイメージに/goServer
としてコピーしています。# Builder image (intermediate container) FROM golang:1.13.4 as builder ENV CGO_ENABLED=0 ENV GOOS=linux ENV GOARCH=amd64 WORKDIR /docker-multi-stage-build-101 COPY . . RUN go build -o "bin/goServer" # Runtime image FROM alpine COPY --from=builder /docker-multi-stage-build-101/bin/goServer /goServer EXPOSE 9000 ENTRYPOINT ["/goServer"]それでは、Dockerfileをビルドして、Dockerイメージのサイズがどれだけスリム化された確認してみましょう。
$ docker build . -f docker/02_multi-stage-build/Dockerfile -t go-server-multi:dev $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE go-server-multi dev 2d5f5819aa81 4 hours ago 21.4MB21.4MBとかなり小さなDockerイメージとなっていることがわかると思います。最初の例では915MBだったので、Dockerイメージのサイズをおよそ1/40程度までスリム化できています。
最後に
Dockerのマルチステージビルドを利用することで、アプリケーションのビルド環境と実行環境の設定を1つのDockerfileに記述することができます。これは、今まで使われていたBuilderパターンなどと比較しても、より簡素で管理のしやすい方法だと感じました。Dockerfileを記述する際は、積極的に使っていくといいのではないかと思います。
参考
Use multi-stage builds
Dockerの公式ドキュメントの説明です。
https://docs.docker.com/develop/develop-images/multistage-build/docker-multi-stage-build-101
今回の記事で使用したサンプルコードになります。
https://github.com/Imamachi-n/docker-multi-stage-build-101BioRxivGo
Code Chrysalisの多言語週間中に作成したフルスタックアプリケーション(Vue, Golang, PostgreSQLなど)になります。こちらのアプリケーションではマルチステージビルドに加えて、Docker composeでアプリケーションを起動しています。
https://github.com/Imamachi-n/BioRxivGo
- 投稿日:2019-12-22T15:13:08+09:00
Dockerで始めるStackstorm再入門2/3(条件分岐させるWorkflowと定期実行させるRuleの書き方)
本記事はDockerで始めるStackstorm再入門(環境構築からOrquestaで書いたWorkflowの結果をslackに通知するところまでのチュートリアル)の第2部です。
0. 目次
本記事では前編で取り上げることができなかったStackstormの構成ファイルの書き方についてお話しできればと思います。
- 1. Actionとは
- 2. Workflowとは
- 3. Workflowで引数や結果に応じて分岐させる書き方(基本編)
- 4. Workflowで引数や結果に応じて分岐させる書き方(shellscript編)
- 5. サンプルWorkflow
- 6. Ruleを使ってAction/Workflowを定期実行
- 7. shellscriptのデバッグについて
- 8. 次回予告
5. サンプルWorkflow
では、リモートリポジトリのステータスの確認とコンテナの再立ち上げをshellscriptをActionの実行関数としながら、workflowを紹介し、
その前の1から4でActionやworkflowの前提知識をお伝えしています。
最後にRuleとshellscriptのデバッグについて少し書いています。1. Actionとは
Stackstormがおこなう処理
のことです。このActionは大きく分けて以下の3種類があります。
- core.localなどStackstormが提供するAction
- サードパーティが提供するAction(Ansible plabookがst2上で扱えるものなど)
- 独自に作ったAction
Actionの構成ファイルとしては、以下の2つです。
- 処理を定義するyamlファイル(メタファイル)
- Actionの実行スクリプト(Action Runnner
Writing Custom Actions
An action is composed of two parts:A YAML metadata file which describes the action, and its inputs.
A script file which implements the action logic
As noted above, an action script can be written in an >arbitrary programming language, as long as it follows these >conventions:
Script should exit with 0 status code on success and non-zero >on error (e.g. 1)
All log messages should be printed to standard error
参考: Action Runners — StackStorm 3.1.0 documentation
処理を定義するActionのメタファイル
ここでは、私が作った
mydemo_pack
というPACKのActionのメタファイルを一例として取り上げます。
やっていることは、cd $working_dir && git fetch -p && git checkout -q $branch && git status
をしてリモートリポジトリの更新を確認することです。/opt/stackstorm/pack/mydemo_pack/actions/git_status.yaml--- name: "git_status" pack: "mydemo_pack" description: "git status" enabled: true runner_type: "local-shell-script" # 後述します entry_point: "scripts/git_status.sh" # 本メタファイルからみた相対パスとなります。 parameters: working_dir: type: "string" required: true position: 0 branch: type: "string" required: true position: 1 expected: type: "string" required: true position: 2公式ドキュメントで紹介されていたrunner_type
このように複数ありますが、だいたい使うのは文字の背景がグレーとなっているものかなと思います。
local-shell-cmd
local-shell-script
remote-shell-cmd
remote-shell-script
python-script
- http-request
- action-chain
mistral-v2
- cloudslang
- inquirer
- winrm-cmd
- winrm-ps-cmd
- winrm-ps-script
参考: Actions — StackStorm 3.1.0 documentation
なお、
orqeusta
については取り上げられていませんが、orqeustaを使う場合、workflowのメタファイルにrunner_type: "orquesta"
と書くことでorquestaを扱うことが出来ました。参考:
st2/orquesta-streaming-demo.meta.yaml at master · StackStorm/st2Actionの実行スクリプト(Action Runnner)
これは先ほど
処理を定義するメタファイル
で取り上げたActionのメタファイルのなかのentry_point: "scripts/git_status.sh"
です。/opt/stackstorm/packs/mydemo_pack/actions/scripts/git_status.sh#!/bin/bash # exit 0以外のリターンコードが返ることがあれば、そこで抜けるようにする set -e working_dir=$1 branch=$2 expected=$3 if [ -d "$working_dir" ]; then cd $working_dir sudo git fetch -p sudo git checkout -q $branch # ローカルリポジトリは最新であることを想定してgit status if [ "$expected" = "up_to_date" ]; then output=$(sudo git status | grep -E "(Your)\s+(branch)\s+(is)\s+(up-to-date)\s+(with)\s+('origin/$branch')" | awk '{print $6}' | grep -oP "$branch") # ローカルリポジトリは最新ではないこと(リモートリポジトリから更新を受け取る必要があること)を想定してgit status elif [ "$expected" = "not_up_to_date" ]; then output=$(sudo git status | grep -E "(Your)\s+(branch)\s+(is)\s+(behind)\s+('origin/$branch')" | awk '{print $5}' | grep -oP "$branch") else echo "None of the condition met" fi output=$(echo ${output:="unknown"}) # git statusの結果が想定通りであるか if [ "$output" = "$branch" ]; then exit 0 fi fi2. Workflowとは
runner_typeでworkflowのmistralとorqeustaが取り上げられたので、ここでお話します。
Workflowとはひとつひとつの処理(Action)を下の図のように繋げたものです。
workflowのメタファイル
workflowのメタファイルの記載項目は、Actionのメタファイルのそれとほとんど変わりません。
強いて言えばメタファイルを書くworkflowをst2 runで引数について定義することなく実行させる場合、default: "hoge"
などと引数の値を定義する必要があるということと、runner_type: "orquesta"
くらいです。workflowはActionと違い、メタファイルの拡張子とworkflow自身のファイルの拡張子が同じyamlと紛らわしいので、私はWorkflowのメタファイルは
*.meta.yaml
と命名しています。/opt/stackstorm/packs/mydemo_pack/actions/poll-repo.meta.yaml--- name: "poll-repo" pack: "mydemo_pack" description: "poll repo" runner_type: "orquesta" entry_point: "workflows/poll-repo.yaml" enabled: true parameters: working_dir: type: "string" required: true default: "/usr/src/app/flask-docker" branch: type: "string" required: true default: "devel-views" expected: type: "string" required: true default: "up_to_date" ptn: type: "string" required: true default: "flask-docker_flask|flask-docker_nginx" timeout: type: "integer" required: true default: 3003. Workflowで引数や結果に応じて分岐させる書き方(基本編)
以下の2つはAction問わず、使うと思います。
- Actionの成否
- 引数や変数の値に応じて
Actionの成否
Actionが成功したというのは、Actionのreturn codeが0であるとき
、失敗したというのは、0以外であるときです。
(筆者調べ)Actionが成功した場合.yaml- when: <% succeeded() %>`Actionが失敗した場合.yaml- when: <% failed() %>`引数や変数の値に応じて
boolean値で分岐させる場合.yamltasks: init: action: core.noop next: - do: doSth publish: - failed: False # boolean値を初期化 doSth: action: xxxx next: - when: <% failed() %> do: last publish: - failed: True # boolean値を更新 last: action: core.noop next: - when: <% ctx().failed %> # boolean値がTrueであるときfailを実行 do: fail #- when: <% not ctx().failed %> # boolean値がFalseであるとき # do: yyyy引数の値に応じて.抜粋.yaml# 「5. サンプルWorkflow」で取り上げているworkflowの抜粋です # expectedはworkflowのメタファイルでdefault値として定義することと併せて、Actionの成否に応じて値を更新(publish)しています next: - when: <% succeeded() and (ctx().expected = 'up_to_date') %> do: last - when: <% succeeded() and (ctx().expected = 'not_up_to_date') %> do: git_merge publish: - failed: False4. Workflowで引数や結果に応じて分岐させる書き方(shellscript編)
shellscriptでActionを実行する場合、Actionの結果に応じてworkflowを条件分岐する際に使うことができるのは、上で取り上げた方法の他には、リターンコードだけのようです。(筆者調べ)
リターンコードで分岐.抜粋.yaml# 「5. サンプルWorkflow」で取り上げているworkflowの抜粋です rebuild_app: # 立ち上がっているコンテナを停止/削除した後、再立ち上げを図る action: mydemo_pack.rebuild_app input: working_dir: <% ctx().working_dir %> ptn: <% ctx().ptn %> timeout: <% ctx().timeout %> next: - when: <% succeeded() %> do: post_msg publish: - failed: False - action_result: <% result() %> - when: <% failed() %> do: check_failed publish: - action_result: <% result() %> - rc: <% result().return_code %> check_failed: # rebuild_appがfailedした原因を調査 action: core.noop next: - when: <% ctx().rc = 201 %> # failedの原因は停止/削除するコンテナがなかったことなので、改めてコンテナの再立ち上げを図る do: rebuild_app_cmd - when: <% ctx().rc != 201 %> do: post_msg publish: - failed: Trueリターンコードはshellscriptでこのように返却しています。
/opt/stackstorm/packs/mydemo_pack/actions/scripts/rebuild_app.sh#!/bin/bash # exit 0以外のリターンコードが返ることがあれば、そこで抜けるようにする set -e working_dir=$1 ptn=$2 counter=0 if [ -d "$working_dir" ]; then cd $working_dir # image idを取得 ids=$(sudo docker container ls | grep -E "${ptn}" | awk '{print $1}') # image idをfor-loopでstop/rmしていき、成功すれば$counterをインクルメント # image idがひとつもなければfor-loopは行われず、$counterもインクルメントされない for i in $ids; do sudo docker container stop $i \ && sudo docker container rm $i; counter=`expr $counter + 1` done # image idから2回特定のコンテナをstop/rmsしている場合 if [ $counter -eq 2 ]; then sudo docker-compose up -d --build counter=`expr $counter + 1` # 1度も特定のコンテナをstop/rmsしていない場合(そもそもコンテナが立ち上がっていない場合) elif [ $counter -eq 0 ]; then exit 201 fi fi if [ $counter -eq 3 ]; then exit 0 fi5. サンプルWorkflow
上のowrkflowの書き方で取り上げたサンプルはこちらから抜粋しています。
/opt/stackstorm/packs/mydemo_pack/actions/workflows/poll-repo.yamlversion: 1.0 description: poll remote repo input: - working_dir - branch - expected - ptn - timeout output: - failed: <% ctx().failed %> - action_name: <% ctx().action_name %> tasks: init: # failedフラグを初期化(False)とするだけのAction action: core.noop next: - publish: - failed: False - action_name: 'poll_repo' do: git_status_before_merged git_tatus_before_merged: # ローカルリポジトリは最新であることを想定してgit status action: mydemo_pack.git_status input: working_dir: <% ctx().working_dir %> branch: <% ctx().branch %> expected: <% ctx().expected %> #デフォルト値は'up_to_date' timeout: <% ctx().timeout %> next: - when: <% succeeded() and (ctx().expected = 'up_to_date') %> do: last - when: <% succeeded() and (ctx().expected = 'not_up_to_date') %> # 'not_up_to_date'(最新ではないこと)が確認できた do: git_merge publish: - failed: False - when: <% failed() and (not ctx().failed) %> do: git_status_before_merged publish: - failed: True - expected: 'not_up_to_date' - when: <% failed() and (ctx().failed) %> do: post_msg publish: - action_result: |- [result] <% result() %> git_merge: # git_status_before_mergedで、ローカルリポジトリは最新ではないことが確認出来きた場合のみ実行 action: core.local input: cmd: sudo git merge origin/<% ctx().branch %> cwd: <% ctx().working_dir %> timeout: <% ctx().timeout %> next: - when: <% succeeded() %> do: git_status_after_merged publish: - expected: 'up_to_date' - when: <% failed() %> do: post_msg publish: - failed: True - action_result: |- [result] <% result() %> git_status_after_merged: # ローカルリポジトリは最新であるとしてgit_status_after_mergedを実行 action: mydemo_pack.git_status input: working_dir: <% ctx().working_dir %> branch: <% ctx().branch %> expected: <% ctx().expected %> timeout: <% ctx().timeout %> next: - when: <% succeeded() %> do: rebuild_app publish: - action_result: |- [result] <% result() %> - when: <% failed() %> do: post_msg publish: - failed: True - action_result: |- [result] <% result() %> rebuild_app: # 立ち上がっているコンテナを停止/削除した後、再立ち上げを図る action: mydemo_pack.rebuild_app input: working_dir: <% ctx().working_dir %> ptn: <% ctx().ptn %> timeout: <% ctx().timeout %> next: - when: <% succeeded() %> do: post_msg publish: - failed: False - action_result: <% result() %> - when: <% failed() %> do: check_failed publish: - action_result: <% result() %> - rc: <% result().return_code %> check_failed: # rebuild_appがfailedした原因を調査 action: core.noop next: - when: <% ctx().rc = 201 %> # failedの原因は停止/削除するコンテナがなかったことなので、改めてコンテナの再立ち上げを図る do: rebuild_app_cmd - when: <% ctx().rc != 201 %> do: post_msg publish: - failed: True rebuild_app_cmd: # docker-compose up -d --buildを実行 action: core.local input: cmd: sudo docker-compose up -d --build cwd: <% ctx().working_dir %> timeout: 600 next: - when: <% succeeded() %> do: post_msg publish: - action_result: |- [result] <% result() %> - when: <% failed() %> do: post_msg publish: - action_result: |- [result] <% result() %> post_msg: # workflownの成否をslackに通知 action: slack.post_message input: message: |- [action_name] <% ctx().action_name %> [failed] <% ctx().failed %> [action_result] <% ctx().action_result %> next: - do: last last: # workflow全体の成否を決める上で考慮するべき失敗したActionがあるかfaildフラグで確認 action: core.noop next: - when: <% ctx().failed %> do: fail # workflow全体の結果をfailedとするorquestaが提供するEngine Commandfailの使いどころ
failは、
orquestaが提供する、workflow全体の結果をfailedとするorquestaが提供
するEngine Commandです。
そもそもworkflowのステータスは最後のActionのステータスによって決まるので、途中のActionの失敗をworkflowのステータスに反映出来ません。
そこでこのような使い方が考えられます。
- workflowのステータスを判定するフラグをworkflowの冒頭で定義
- このフラグはworkflowの途中のActionなど任意のタイミングで値を更新
- 最後のActionでそのフラグに応じてfailを実行
The workflow engine will fail the workflow execution.
参考: Orquesta Workflow Definition — StackStorm 3.1.0 documentation6. Ruleを使ってAction/Workflowを定期実行
以下のyamlを作ってください。
root@$ID:/# cat /opt/stackstorm/packs/mydemo_pack/rules/timer.yaml --- name: "timer" pack: "mydemo_pack" description: "run mydemo per 300 secs" enabled: true trigger: type: "core.st2.IntervalTimer" parameters: unit: seconds #delta: 30 delta: 300 action: ref: "mydemo_pack.poll-repo" parameters: working_dir: "/usr/src/app/flask-docker" branch: "devel-views" expected: "up_to_date" ptn: "flask-docker_flask|flask-docker_nginx"それではRuleを作るコマンドを以下のとおり実行すれば設定完了です。
root@$ID:/# st2 rule create timer.yaml7. shellscriptのデバッグについて
以下の2つの方法でデバッグさせていました。
- Action単体で実行
root@$ID:/# st2 run mydmeo_pack.git_status
- bash -x /path/to/$filenameでデバッグ
root@$ID:/# bash -x /opt/stackstorm/packs/mydemo_pack/actions/scripts/git_status.sh \ > /usr/src/app/flask-docker \ > flask-docker_flask|flask-docker_nginx8. 次回予告
shellscriptでリモートリポジトリのステータスの確認とコンテナの再立ち上げを行いましたが、今度はそれをpythonでおこなってみます。
pythonでActionの実行スクリプトを書くと、shellscriptに比べてActionの返却値(return value)を柔軟に定義
することができたり、mockを使ってテストコードも書く
ことができるので、よりきめ細かくActionを書くことができます。お楽しみに!!
鋭意作成中
Dockerで始めるStackstorm再入門3/3(pythonスクリプトの書き方とmockを使ったリファクタリング)参考
orqeustaのサンプルコード
Ansible pack
Slack pack
本記事で扱ったStackstormのサンプルコード
Stackstormの管理対象のアプリコンテナ
ActionとWorkflowのサンプルコード
P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)
- 投稿日:2019-12-22T01:17:15+09:00
Spring Boot+Thymeleaf+DockerでMiRmの基幹システムを開発した (PWA対応)
普通科高校の2年生をやっている@itsu_devです。
今回はMiRm(Minecraft 無料マルチプレイサーバーホスティングサービス)の基幹システムの入れ替えに伴い、@haniokasai氏と新しくシステムを開発しなおしたのでそれを紹介します。はじめに
そもそもMiRmとは?
Minecraftを友達としようにも、
- サーバーが必要
- ソフトの導入・設定が面倒
- 環境構築が難しい
- 維持費がかかる
といった欠点があり、なかなか敷居の高いものでした。
そこでこれらすべてを解決すべく始まったのがMiRm Projectです。MiRmにはどんな機能があるの?
- サーバーソフトの標準出力のリアルタイム表示
- サーバーソフトへの標準入力
- FTP経由でのファイル操作
- ワンタッチでのサーバー起動・停止
- Markdown記法によるサーバーリストへの紹介文掲載
こういった機能をメインに、ほかにも様々な機能をユーザーに提供しています。
技術的な内容
使用した技術
タイトルにもあるように、Spring BootベースのWebアプリケーションとしてすべてを完結させています。「スマホだけでできる」がウリなので、もちろんPWAにも対応しています。
各ユーザーが所有するサーバーは一つのDockerコンテナで完結しており、ファイル操作やサーバーソフトの動作もすべてこの中で行っています。他の主なものとしては
- Thymeleaf(htmlテンプレートエンジン)
- MySQL
- Material Design Bootstrap(Bootstrapのマテリアルデザインライブラリ)
- jQuery
- SimpleMDE(埋め込みMarkdownエディタ)
- darkmode.js(ダークモードの実装)
- kotlin coroutine
といったところです。
全体を管理する基幹システムはkotlinで書かれています。全体構成
基幹システムとDockerコンテナとの接続
基本的にはDocker-JavaとJava標準のProcess経由でのコマンド実行の両方を使っています。
Minecraftのコマンドを標準入力に書き込むのに、普通に
docker attach CONTAINER
をProcess経由で実行して得たそれに対して行ってもよいのですが、それだとある問題が発生します。
それは、コンテナ内部のサーバーソフトがPID 1で動作していないことです。
この問題によってProcessで得られる標準入力に書き込んでもサーバーソフトに書きこまれない現象が発生しました。それを解決するために、コマンドを/proc/PID/fd/0
に書き込むことにしました。以下のコードでは、上記のPIDを得るためにdocker topコマンドの内容をパースしています。
※MiRmDockerClient#getCommandExceptList サーバーソフトのプロセスを取得するために除外する文字列のリスト
※TopElementクラス 出力内容のオブジェクトクラスpublic static List<TopElement> parse(String serverId) { ArrayList<TopElement> elements = new ArrayList<>(); LinkedList<String> outputs = new LinkedList<>(); try { StringBuffer buf = new StringBuffer(); buf.append("docker -H "+ DOCKERHOST + " top " + serverId + MiRmDockerClient.getPrefix()); MiRmDockerClient.getCommandExceptList().forEach(str -> buf.append(" | grep -v \"" + str + "\"")); ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", buf.toString()); Process process = pb.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); String temp; while ((temp = reader.readLine()) != null) { outputs.add(temp); } reader.close(); } catch (IOException e) { e.printStackTrace(); } outputs.forEach(str -> { str = str.replaceAll("\\s+", " "); if (!str.startsWith("UID")) { String[] data = str.split(" "); StringBuffer buf = new StringBuffer(); for (int i = 7; i < data.length; i++) { buf.append(" " + data[i]); } elements.add(new TopElement( data[0], Long.parseLong(data[1]), Long.parseLong(data[2]), Integer.parseInt(data[3]), data[4], data[5], data[6], buf.toString().substring(1) )); } }); return elements; }SimpleMDE
超簡単にMarkdownエディタをWebページに実装可能な"SimpleMDE"を参考にさせていただきました。ありがとうございます。
MiRmでサーバーリストを表示するのに、各サーバーに紹介文を書いてもらいたかったのですが、普通の文章だとみんな同じで目立たなくなってしまう。とはいえhtmlを敷居が高い...
と思った矢先、SimpleMDEというjsライブラリを見つけたのでこれを使用して簡単なMarkdownに対応させることにしました。右下のLobiのアイコンはフォントを作成して表示しています。(参考:https://nelog.jp/feedly-web-iconic-font)
編集はMarkdownですが、表示はhtmlで行っています。この変換もSimpleMDEに標準でついており、簡単に使用することができます。
以下のコードでtextareaをエディタ化しています。
toolbarの部分でエディタ上部のツールバーをカスタマイズできます。<html> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"> </head> <body> <label for="description">マークダウン記法が使えます。(htmlタグは使用不可・ボタンとして載っている記法のみ使用可能)</label> <textarea class="form-control z-depth-1" id="description" name="description" rows="8" cols="40" placeholder="xxサーバーへようこそ!" th:text="${description}"> </textarea> <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script> <script> var simplemde = new SimpleMDE({ element: document.getElementById("description"), forceSync: true, spellChecker: false, toolbar: ["bold", "italic", "strikethrough", "heading", "|", "quote", "unordered-list", "ordered-list", "link", "|", "preview", "side-by-side", "guide"] }); marked.setOptions({ sanitize: true, sanitizer: escape, breaks: true }); </script> </body> </html>今後の展望
コントロールパネルや設定画面等、コンテンツの中身が多いがゆえに表示速度が若干遅いので、その辺をもっと改善していきたいと思っています。
また将来的にはより簡単に操作できるバージョンのリリースも考えています。
- 投稿日:2019-12-22T00:18:53+09:00
KafkaからKSQLまで一気にハンズオン入門
目的
本記事はハンズオンを通してApache Kafkaに触れ、少しでも多くの方にKafkaの良さを理解していただくことを目的としています。細かいKafkaの実装や仕組みについては割愛し、実際にKafkaを用いることでどのような処理が可能になるのか、既存の問題に対して解決策となるのかなどイメージを膨らませられる機会となれば幸いです。
Kafka入門
Kafkaについて一通りの基礎を理解されている方は読み飛ばして頂いて問題ありません。
Kafkaとは2011年LinkedInにより「分散メッセージングキュー」として発表されました。現在Kafkaの公式ページには「分散ストリーミングプラットフォーム」と記載されておりますが、基本的にはメッセージングキューとして認識して頂いて問題ないかと思います。
以下のような特徴を持ち、柔軟でスケーラブルかつ耐障害性を兼ね備えたメッセージングプラットフォームとして様々な大規模システムで採用されています。
- Pub/Subモデル => 同じメッセージを複数のアプリが受信可能(柔軟・スケーラブル)
- マルチブローカーによるクラスタ構成 => メッセージ量によりサーバーを増やし高スループットを実現
- メッセージデータのディスク保存による永続化 => 同じメッセージを再度読み込むことでメッセージの再処理が可能
また成熟したコミュニティから様々な言語でのAPIやKafka Connectと呼ばれる豊富なプラグインが提供されており、開発者にとっても優しい環境が揃っています。
Kafkaの用語と簡単な仕組み
Kafkaにはそれぞれ役割に応じた用語が使われており、大まかに以下のような構成なっています。
メッセージ送信側:Producer
メッセージ受信側:Consumer
メッセージ仲介役:Broker
各メッセージキューイング:Topic
Topicのキューをシャーディングしたキューイング:Partition
さらにKafkaのクラスタ管理にはZookeeperの起動が必要です。ハンズオン
概要はここまでにして、実際に手を動かしてみましょう。
今回は以下の環境でハンズオンを進めていきます。macOS: 10.14
python: 3.7.4
docker: 2.1.0.5
kafka-docker: https://github.com/wurstmeister/kafka-docker
KSQL: https://github.com/confluentinc/ksql#1 Kafkaをdocker上で起動
#1.1 準備
まずはkafka-dockerをローカルにクローンしてきましょう。
適当にローカル環境にディレクトリを作成し、githubよりクローンします。mkdir ~/kafka && cd ~/kafka git clone https://github.com/wurstmeister/kafka-docker.git cd kafka-dockerkafka-dockerよりdocker-compose.ymlが提供されているので、そのまま
docker-compose up -d
を実施したいところですが、こちらのファイルに少し修正が必要です。
ref) https://github.com/wurstmeister/kafka-docker#advertised-hostname
に記載されているようにadvertised ipを設定する必要があります。
KAFKA_ADVERTISED_HOST_NAME: 192.168.99.100
と直書きされているIPアドレスを環境変数DOCKER_HOST_IP
に変更しておきます。sed -i -e 's/KAFKA_ADVERTISED_HOST_NAME:.*/KAFKA_ADVERTISED_HOST_NAME: ${DOCKER_HOST_IP}/g' docker-compose.yml次に起動したKafkaに事前にTopicを生成しておきたい場合、次の値を設定すると便利です。
ref) https://github.com/wurstmeister/kafka-docker#automatically-create-topics
先ほど変更した変更したKAFKA_ADVERTISED_HOST_NAME
の次の行に以下を挿入してください。KAFKA_CREATE_TOPICS: "topic1:3:2,topic2:3:2
以上で準備完了です。
それではKafkaを起動してみましょう。#1.2 Kafkaの起動
# .bashrcなどshell起動時に設定されるようにしておくといいでしょう export DOCKER_HOST_IP=$(ipconfig getifaddr en0) docker-compose up -d --build docker-compose ps # ポート番号は異なる場合もあります。 # Name Command State Ports # ---------------------------------------------------------------------------------------------------------------------- # kafka-docker_kafka_1 start-kafka.sh Up 0.0.0.0:32771->9092/tcp # kafka-docker_zookeeper_1 /bin/sh -c /usr/sbin/sshd ... Up 0.0.0.0:2181->2181/tcp, 22/tcp, 2888/tcp, 3888/tcpBrokerの数を3つに増やします。
docker-compose scale kafka=3 docker-compose ps # Name Command State Ports # ---------------------------------------------------------------------------------------------------------------------- # kafka-docker_kafka_1 start-kafka.sh Up 0.0.0.0:32771->9092/tcp # kafka-docker_kafka_2 start-kafka.sh Up 0.0.0.0:32772->9092/tcp # kafka-docker_kafka_3 start-kafka.sh Up 0.0.0.0:32773->9092/tcp # kafka-docker_zookeeper_1 /bin/sh -c /usr/sbin/sshd ... Up 0.0.0.0:2181->2181/tcp, 22/tcp, 2888/tcp, 3888/tcp#1.3 Kafka動作確認
それでは、実際にKafkaをCLIで操作してみましょう。
# dockerコンテナ内にアクセス ./start-kafka-shell.sh $DOCKER_HOST_IP # Broker情報が出力 bash-4.4# broker-list.sh # 10.XXX.XXX.XXX:32772,10.XXX.XXX.XXX:32773 # 10.XXX.XXX.XXX:32771 # docker-compose.ymlのKAFKA_CREATE_TOPICSに指定したTopicが生成されていることを確認 bash-4.4# $KAFKA_HOME/bin/kafka-topics.sh --list --bootstrap-server `broker-list.sh` # topic1 # topic2 # Topicの作成 bash-4.4# $KAFKA_HOME/bin/kafka-topics.sh --create --topic topic-from-cli --partitions 3 --replication-factor 2 --bootstrap-server `broker-list.sh` bash-4.4# $KAFKA_HOME/bin/kafka-topics.sh --list --bootstrap-server `broker-list.sh` # topic-from-cli # topic1 # topic2以上で、簡単なKafkaの動作確認は終了です。
クローンしたリポジトリにはProducerやConsumerもCLIで試せるshファイルが用意されていますので、そちらも試してみると良いでしょう。
実際のシステムではCLI経由でProducer/Consumerを実装することはほとんどないと思いますので、次はPython3を使ったProducerを作成し、アプリ経由でTopicにメッセージを送信できるようにしましょう。#2 Kafkaへメッセージを送信 - Producerの実装
#2.1 準備
Python3のKafkaライブラリをインストールしましょう。各々足りないモジュールは適宜インストールしてください。
cd ~/kafka pip install kafka-python続けて以下のファイルを作成します。私自身Python自体普段書きません。
あくまで動作確認レベルのコードです。topic1-producer.pyrom kafka import KafkaProducer from datetime import datetime import subprocess import json import random cwd_name = subprocess.check_output("pwd").decode('utf-8').rstrip('\n') + "/kafka-docker" host_ip = subprocess.check_output("ipconfig getifaddr en0", shell=True).decode('utf-8').rstrip('\n') netstat_result = subprocess.check_output("DOCKER_HOST_IP=${host_ip} && docker-compose exec kafka netstat |awk '{ print $5 }' |grep '^1.*:32.*'", cwd=cwd_name, shell=True).decode('utf-8').rstrip('\n') kafka_ips = list(set(netstat_result.split('\n'))) # print(kafka_ips) date = datetime.now().strftime("%Y/%m/%d") messageId = datetime.now().strftime("%Y/%m/%d-%H:%M:%S:%f") user_id = random.choice([1000, 2000, 3000]) word_id = random.randint(1,5) word_pattern = {1: 'hello', 2: 'world', 3: 'hoge', 4: 'fuga', 5: 'hello world'} word_count = random.randint(1,3) word_keys = random.sample(word_pattern.keys(), word_count) producer = KafkaProducer(bootstrap_servers=kafka_ips, value_serializer=lambda m: json.dumps(m).encode('utf-8')) for word_type in word_keys: kafka_msg = {'userId': user_id, 'messageId': messageId, 'message': {'wordId': word_type, 'word': word_pattern[word_type]}} producer.send('topic1', key=date.encode('utf-8'), value=kafka_msg).get(timeout=1)#2.2 Kafkaにメッセージを送信
2つのターミナルタブを使います。
1つはトピック内のメッセージ確認用、もう1つはメッセージ送信用です。# tab1 # Kafka CLI起動 ./start-kafka-shell.sh $DOCKER_HOST_IP # Consumer起動 # --from-beginningオプションをつけると既にTopicに届いているメッセージを表示することが可能 bash-4.4# $KAFKA_HOME/bin/kafka-console-consumer.sh --topic=topic1 --from-beginning --bootstrap-server `broker-list.sh` --- # tab2 python topic1-producer.pytab2のPythonスクリプトを実行するとtab1側に
{"userId": 1000, "messageId": "2019/12/21-22:46:03:131468", "message": {"wordId": 2, "word": "world"}}のようにメッセージが流れてくることが確認できるはずです。
以下のようにスクリプトを実行すると3秒ごとにメッセージが到達することがわかるでしょう。# bash while true; do python topic1-producer.py; sleep 3s; done; # fish while true; python topic1-producer.py; sleep 3s; end;#2.3 メッセージ到着の様子
#3 KSQLを使ったStreaming処理の実装
続いてストリーミング処理を行ってみましょう。ストリーミングといっても、特殊なことはなく延々とTopicに流れるメッセージ(イベント)全体を「ストリーミング」と呼んでいるにすぎません。KSQLはそれらの流れているイベントに対してSQLライクにクエリを投げてフィルタや集計を行うことができるAPIです。Topicに流れてくるメッセージの連続データを別の連続データ(Stream)や集計データ(Table)に変化させ、そのデータを新たなトピックとし別のアプリケーションで処理を行うことができるというものです。詳細は下記のリンクを参照してみてください。
ref) https://kafka.apache.org/documentation/streams/
ref) https://www.youtube.com/watch?v=DPGn-j7yD68StreamやTableは基本的に(24/7)常時稼働しているもので、Topicと同じ扱いと認識しておくとスッと入りやすいかと思います。
#3.1 準備
まずはconfluent社の開発したKSQLを準備します。
cd ~/kafka git clone https://github.com/confluentinc/ksql.git cd ksql#3.2 KSQL server / KSQL CLIの起動
# kafka-dockerディレクトリに戻る cd ../kafka-docker # kafkaの起動中IPアドレス+Port番号取得 export KSQL_BOOTSTRAP_SERVERS=(docker-compose exec kafka netstat |awk '{ print $5 }' |grep '^1.*:32.*' |sort |uniq |tr '\n' ',') # ksqlディレクトリへ移動 cd ../ksql # ksql server起動 docker run -d -p $DOCKER_HOST_IP:8088:8088 \ -e KSQL_BOOTSTRAP_SERVERS=$KSQL_BOOTSTRAP_SERVERS \ -e KSQL_OPTS="-Dksql.service.id=ksql_service_3_ -Dlisteners=http://0.0.0.0:8088/" \ confluentinc/cp-ksql-server:5.3.1 # dockerプロセス確認 docker ps # confluentinc/cp-ksql-server:5.3.1のコンテナが起動していること # KSQL CLI起動 docker run -it confluentinc/cp-ksql-cli http://$DOCKER_HOST_IP:8088KSQLのCLI起動が成功すると下のようなCLIが立ち上がります。
#3.3 Streamの作成
ここでは、topic1,2からstreaming処理用のstream, tableを作成してみます。
ksql> show streams; # Stream Name | Kafka Topic | Format # ------------------------------------ # ------------------------------------ ksql> CREATE STREAM topic1_stream1 (userId INT, messageId VARCHAR, message STRUCT<word VARCHAR, wordId INT>) WITH (KAFKA_TOPIC = 'topic1', VALUE_FORMAT='JSON', KEY='userId'); ksql> show streams; # Stream Name | Kafka Topic | Format # --------------------------------------- # TOPIC1_STREAM1 | topic1 | JSON # --------------------------------------- ksql> CREATE TABLE topic1_table1 (userId INT, wordCount INT, message STRUCT<word VARCHAR, wordId INT>) WITH (KAFKA_TOPIC = 'topic1', VALUE_FORMAT='JSON', KEY='userId'); ksql> show tables; # Table Name | Kafka Topic | Format | Windowed # ------------------------------------------------- # TOPIC1_TABLE1 | topic1 | JSON | false # -------------------------------------------------※重要
Stream、Tableを作る際にはいくつか制限があります。私自身このルールを覚えるまで色々試行錯誤が必要でした。
ref) https://docs.confluent.io/current/ksql/docs/developer-guide/join-streams-and-tables.html
from Topic from stream from stream-stream from table-table from stream-table CREATE Stream o o o x o CREATE Table o o x o x SQL同様、2つのリソースから新しいStream, Tableを作成するためには
JOIN
構文を用います。ここで注意が必要なのは各リソースのKEYに設定された値でのみJOIN
が可能であるという点です。つまり、上の例ではtopic1から作成したStreamと別のtopicから作成されたStreamにおいて2つのカラムでJOIN
することはできないということです。(例:userId=2000 and wordCount=5のイベントを新Streamとすることはできない。)複数のカラムで
JOIN
したい場合は、Topicのメッセージにそれらを組み合わせたカラムを用意しKEY
とすることで対応可能です。(例:KEY =>${userId}-${wordCount}
)また、Tableへのクエリで
GROUP BY
をするためにも対象がKEY
である必要があります。#3.4 Streamへのクエリ
Streamへのクエリは常に更新分のメッセージに対して行われます。つまり、クエリを投げた時点よりも前にTopicへ詰められたメッセージはStreamへのクエリ結果として出力されません。この章の冒頭で述べたようにStreamやTableは常時稼働しておくものでTopicと同じように事前に作成するものです。KSQLを触りたての時期はその認識が抜けているため、「結局何に使うの?いつ使うの?」という疑問が残ってしまうかもしれません。実際のシステムではCLI経由でStream処理を行うことはほとんどないと思いますが、ハンズオンのためデバッグの意味も込めて下記のように既にTopicに入っているメッセージに対してもクエリ結果が確認できるように以下の値をKSQLのCLIで設定しましょう。
ksql> SET 'auto.offset.reset'='earliest';# Stream内全てのeventを取得 ksql> select * from topic1_stream1; # 1576936834754 | 2019/12/21 | 3000 | 2019/12/21-23:00:34:614230 | {WORD=fuga, WORDID=4} # 1576936837399 | 2019/12/21 | 1000 | 2019/12/21-23:00:37:275858 | {WORD=hello world, WORDID=5} # 1576936837512 | 2019/12/21 | 1000 | 2019/12/21-23:00:37:275858 | {WORD=hoge, WORDID=3} --- # Stream内で各ユーザーが同じタイミングでいくつのメッセージを送信したのか ksql> select userId, count(messageId) from topic1_stream1 group by userId, messageId; # 1000 | 3 # 3000 | 2 # 3000 | 1集計関数はKSQLでデフォルトで用意されているものに加えて、開発者が定義したものを使うことも可能です。
ref) https://docs.confluent.io/current/ksql/docs/developer-guide/syntax-reference.html#aggregate-functionsまた、集計に関しては下記のドキュメントが非常に参考になります。特定の時間にイベントを区切って集計ができたりするなど非常に幅広いクエリが可能になっています。
ref) https://docs.confluent.io/current/ksql/docs/developer-guide/aggregate-streaming-data.html#aggregate-streaming-data-with-ksqlこの状態で#2.2の手順によりtopic1へメッセージを送信してみてください。
#3.4 Stream + Stream => Stream => Table
最後に応用編として2つのStreamから新たなStreamを作成し、そこに対してクエリをかけTableを作成してみましょう。
例としてくじ引きでランダムにユーザーが選出され、そのユーザーが過去60分間に発言していたキーワードを抽出するシーンを想定しましょう。
(良い例が浮かばなかったのでご容赦ください;;)まずは
topic1-producer.py
をコピーしtopic2-producer.py
を作成しましょう。cp topic{1,2}-producer.pytopic2-producer.pyfrom kafka import KafkaProducer from datetime import datetime import subprocess import json import random cwd_name = subprocess.check_output("pwd").decode('utf-8').rstrip('\n') + "/kafka-docker" host_ip = subprocess.check_output("ipconfig getifaddr en0", shell=True).decode('utf-8').rstrip('\n') netstat_result = subprocess.check_output("DOCKER_HOST_IP=${host_ip} && docker-compose exec kafka netstat |awk '{ print $5 }' |grep '^1.*:32.*'", cwd=cwd_name, shell=True).decode('utf-8').rstrip('\n') kafka_ips = list(set(netstat_result.split('\n'))) # print(kafka_ips) date = datetime.now().strftime("%Y/%m/%d") user_id = random.choice([1000, 2000, 3000]) producer = KafkaProducer(bootstrap_servers=kafka_ips, value_serializer=lambda m: json.dumps(m).encode('utf-8')) kafka_msg = {'userId': user_id} producer.send('topic2', key=date.encode('utf-8'), value=kafka_msg).get(timeout=1)上記のようにファイルを作成したら、Topic1, Topic2から
userId
をKEYとしたStreamを作成しましょう。ksql> CREATE STREAM topic2_stream1 (userId INTEGER) WITH (KAFKA_TOPIC = 'topic2', VALUE_FORMAT='JSON', KEY='userId'); ksql> show streams; # Stream Name | Kafka Topic | Format # --------------------------------------- # TOPIC2_STREAM1 | topic2 | JSON # TOPIC1_STREAM1 | topic1 | JSON # ---------------------------------------そして、2つのStreamから一致する
userId
から新たなStreamを作成します。Topic2に新しいメッセージ(イベント)が届いたことをトリガーとしているので、Topic2がLEFT側のStreamとなります。
ref) https://docs.confluent.io/current/ksql/docs/developer-guide/join-streams-and-tables.html#semantics-of-stream-stream-joins# Topic2 Stream + Topic1 Stream => New Stream ksql> CREATE STREAM topic1_topic2_stream1 AS SELECT t2s1.userId as userId, t1s1.messageId, t1s1.message FROM topic2_stream1 t2s1 INNER JOIN topic1_stream1 t1s1 WITHIN 1 HOURS ON t2s1.userId = t1s1.userId; # 3.4で SET 'auto.offset.reset'='earliest'; を行った方は下記のコマンドで変更分だけがクエリ結果になるようデフォルトに戻しましょう。 ksql> SET 'auto.offset.reset'='latest'; ksql> select * from topic1_topic2_stream1;この状態で別タブから
topic2-producer.py
を実行してみてください。
実行すると下記のように過去1時間にtopic1_stream1
に届いたメッセージ(イベント)が表示されるでしょう。
それでは最後に
topic1_topic2_stream1
のStreamに対するクエリからTableを作成してみましょう。# StreamへのクエリからTable作成 ksql> CREATE TABLE topic1_topic2_table1 AS SELECT userId, COLLECT_SET(message->word) as word FROM topic1_topic2_stream1 GROUP BY userId; # Topic2にメッセージを送信しながら下記クエリを実行すると、新しくメッセージ(イベント)が作成される様子が確認可能 ksql> select * from topic1_topic2_table1; # 1576940945888 | 2019/12/22 | 1000 | [hello, hello world, fuga, hoge, world] # 1576941043356 | 2019/12/22 | 3000 | [hello, hello world, fuga]以上でハンズオンの内容は終了です。