- 投稿日:2020-01-13T23:47:03+09:00
GKEのコンテナネイティブ負荷分散が想像以上に効果的だった話
背景
GKEのオートスケールは、便利ですがいくつか不満もありました。例えば、ノードによってトラフィックの割合が均一化されないと言う問題です。オートスケーラによってPodが増えた場合、新しいPodへのトラフィックはすぐに均一化される訳ではなく、段階的に上昇していきます。これはk8sのデフォルトでの負荷分散の仕組みに起因するものだと思うのですが、新しく作成されたPodが直ちに有効活用されません。この影響で、急なスパイクには対応しにくいですし、安全に運用するためにはオートスケールの条件を緩める必要がありました。
以下のチャートは、Podごとに負荷の推移を観察したものです。新しく作成されたPodの負荷が、既存のPodと同じ水準の負荷に増えるまで、2~3時間かかってるのがわかると思います。
コンテナネイティブ負荷分散を使ってみる
去年の10月、GKEでコンテナネイティブ負荷分散が正式リリースされました。
https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing?hl=ja既存の負荷分散だと、
LB -> インスタンスグループ -> ノード -> Pod
と言う流れでトラフィックが送られます。
コンテナネイティブ負荷分散だと、LB -> Network Endpoint Group -> Pod
と言う流れになり、ノードを介さずに分散されます(Network Endpoint Groupに関しての詳細はドキュメントを見ていただけると)。コンテナネイティブ負荷分散を既存のサービスで有効にする
留意点はいくつかありますが、コンテナネイティブ負荷分散を使用するために行うことは非常に簡単です。serviceリソースのmetadataに以下のannotationを追記するだけです。
apiVersion: v1 kind: Service metadata: name: service名 annotations: cloud.google.com/neg: '{"ingress": true}' # これを追記する反映されるまでに4~5分かかります。稼働中のサービスで行う場合、一時的にトラフィックが途切れるので注意してください。
目に見えてトラフィックが均一化された!
コンテナネイティブ負荷分散を有効にした結果、Podへのトラフィックが均一化され、Podごとの負荷も均一になりました。
以下のチャートは、コンテナネイティブ負荷分散を有効にする前と後で、同じ時間帯(4時〜12時)におけるPodごとの負荷を観察したものです。
Before
After
有効にする前と比較すると、明らかにPodごとの負荷が均一になっていますね。またそれだけではなく、オートスケーラーによって新しく作成されたPodの負荷も、直ちに既存のPodと同じ水準まで増加しています。
まとめ
と言うわけで、コンテナネイティブ負荷分散をGKEで使ってみたわけですが・・・これはGKEでオートスケールを利用する場合はほぼ必須と言ってもいいくらい効果があると感じました。実際にこれを利用することで、安定運用に必要なノードの台数を削減することが出来ました。
今回扱ったコンテナネイティブ負荷分散はGCPのネットワークエンドポイントを利用するものですが、k8s側で同様の機能が提供されればAWS等他のプロバイダでも使えるので、今後はその辺にも期待したいです。
- 投稿日:2020-01-13T23:30:22+09:00
Dockerでnginx等を使う時はまずLinuxの知識をだな......(戒め)
DockerとかVagrantでnginx等を使う時はまずLinuxの知識をだな......(戒め)
1. 背景
フロント学んできて、そろそろバックエンドもまなんでみようと思い、せっかくなので流行りのDokcerを使用しPHPでサーバーに画像をアップロードするコードを書いた。
そしてハマった2. 環境
・windows10<VirtualBox(vagrant)<CentOS7<Docker
起動構成
docker-compose.ymlversion: '3' services: web: #ver 1.17.7 build: ./nginx ports: - '8080:80' links: - php-fpm volumes: - ./data/public:/var/www/html/public depends_on: - php-fpm php-fpm: #ver 7.3 build: ./php-fpm links: - db volumes: - ./data:/var/www/html3. ハマったところ
このPHPの一行からなるエラーで無限に悩んだ
upload.phpmove_uploaded_file($_FILES['image']['tmp_name'], $savePath);Permisson denied ......
4.1 まずやったこと
とりあえず浅いLinuxの知識を用いて画像保存フォルダの権限を
$sudo chmod 777 images
としてコードが動くのを確認。
もちろんこんなセキュリティガバガバな権限は例えローカルサーバーでも許せなかったので却下4.2 解決策
www-dataがUSERだと判明。
じゃあwww-dataに権限付与してあげればよさそうvagrant環境下の権限はこう
$ls -l ~ drwxrwxr-x. 2 vagrant vagrant 4096 Jan 13 08:32 images ~ $ sudo chown www-data images chown: invalid user: ‘www-data’有効ではないと告げられたのでユーザー一覧を確認してみます
$cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin ~ vagrant:x:1000:1000:vagrant:/home/vagrant:/bin/bash apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologinこれ以外にも色々出てきたけどwww-dataユーザーなんて存在しない。
どうすればいいのか
コンテナを立ち上げて
docker exec -it [id] bash
でphp-fpmコンテナに入ってみる。
その中のファイル権限はdrwxrwxr-x. 2 1000 1000 4096 Jan 13 08:32 images1000 1000?
調べてみたらvagrantユーザーのuidが1000らしい。
これも
$cat /etc/passwd
でユーザー一覧を確認するとvagrant:x:1000:1000:vagrant:/home/vagrant:/bin/bashvagrntのuidに1000番が付けられてるのが分かる
VMという仮想環境の中にさらに仮想コンテナがあってそのコンテナ内では1000番uidに名前がついていないという事らしい
つまりコンテナ内では1000番にvagrntユーザーも含めユーザーは存在してないという事
でもuidは共通みたいだならばコンテナ内から確認すればwww-dataがいるはず......
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologinいた
結局どうすればいいのか
正解なのかは分からないけど思いついたのはコンテナ外で33番UIDでグループがvagrantのユーザーを作ってimagesフォルダの持ち主を33番に変える。
これが自分の中でしっくりきました。
というかこれ以外成功してない$useradd -u 33 www-data -g vagrant $chown www-data images他にも
https://gtrt7.com/blog/nginx/docker_userid_share#3docker-composeyml
のようにしてvagrant側からではなくdocker側から合わせればいけるらしい。自分はこんなエラー出てわからんくて投げてしまいましたが
web_1 | 2020/01/13 13:52:54 [emerg] 1#1: host not found in upstream "php-fpm" in /etc/nginx/conf.d/default.conf:20 web_1 | nginx: [emerg] host not found in upstream "php-fpm" in /etc/nginx/conf.d/default.conf:20default.conf:20
fastcgi_pass php-fpm:9000;
正直分からん
Dockerコンテナ内でファイル関係は完結させた方がいいんだろうなと感じてはいるもののいいやり方が浮かばず今回は断念。
ちょっと深堀するにはLinuxやnginx php-fpm等の知識が足りないので今はphpでファイルアップロードしたフォルダを作るさいは適宜権限変更するこのやり方のまま進んで、強くなったら戻ってこようと思いました。
いいやりかたあったら教えてほしい......
- 投稿日:2020-01-13T22:26:10+09:00
Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)
はじめに
Dockerの公式Tutorialでは、DB:PostgreSQLで解説されている為、他のDB(例:MySQL)でDockerを導入してみたい初学者にとってはややハードルが高い状況です。
本記事では、途中まで作ったRailsアプリ(DB:MySQL)にDockerを導入する方法を解説します。
Dockerを途中から導入するアプリの例としては、scaffoldで作成した
簡単な蔵書管理アプリ(bookapp)を使用します。
(適宜、ご自身で開発中のアプリに置き換えて頂ければと思います)DBはMySQL5.7で実装を進めます。
併せてDocker環境でもDB内容を視覚的に確認できるように
SequelProとの接続方法も解説します。前提
- macでDockerが既に導入されている
Dockerインストールはこちらの記事が分かりやすいです。
DockerをMacにインストールする- Sequelpro インストール済
Sequelproの解説記事はこちら
データベースの操作がGUIで分かりやすく操作できるSequel Proの最低限の使い方目指すゴール
(1) 既存のRailsアプリにMySQLを使ったDocker環境を構築する
(2) Sequelproと接続し、Docker環境でもGUIでDB内容を確認できるようにする
実装の流れ
1 Dockerfile/docker-compose.ymlを作成
2 Dockerfileを編集
3 docker-compose.ymlを編集
4 database.yml編集
5 コンテナをbuildする docker-compose build
6 Dockerコンテナ上でDB作成&migrationを実行
docker-compose run web bundle exec rake db:create / db:migrate7 コンテナを起動する docker-compose up
8 DB接続設定を環境変数に置き換え - env_file
9 Docker環境のRailsアプリとSequelProを接続する
1 Dockerfile/docker-compose.ymlを作成
まずは既存アプリに必要なファイルを追加します。
具体的には(1)Dockerfile (2) docker-compose.ymlの2ファイルです。アプリのルートディレクトリに以下のように2つのファイルを追加してください。
(ファイルの作成場所に注意してください)bookapp ----|-- app |-- bin |-- config |-- db ・・・・・・ ・・・・・・ |-- Gemfile |-- Gemfile.lock |-- package.json |-- Rakefile |-- README.md |-- Dockerfile #ここに追加 |-- docker-compose.yml #ここに追加[解説]
Gemfile/Gemfile.lockについて
・アプリ開発当初からDockerを導入する記事では、ここでGemfile(railsのみ導入)/Gemfile.lock(空でOK)を作成するように記載されていますが、既に開発中のアプリで作成済かと思いますのでGemfileについては特に何もしなくてOKです。2 Dockerfileを編集
次にDockerfileを以下のように編集します。
DockerfileFROM ruby:2.5 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN gem install bundler RUN bundle install ADD . $APP_ROOT[解説]
RUN gem install bundlerについて
Railsアプリに途中からDockerを導入する場合、Gemfileが既にある関係で上記のコマンド無しで進めようとするとエラーが出るかもしれません。(自分の環境ではBuildした時に以下のようなエラーが出ました)ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 20対策として、DockerfileをBuildする際にbundlerを導入しておきます。
3 docker-compose.ymlを編集
続いて docker-compose.ymlを以下のように編集します。
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 'password' ports: - "4306:3306" web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app_name ports: - "3000:3000" depends_on: - db[解説]
1 db:portsの設定について- "4306:3306"
DockerコンテナとSequelpro接続の為に必要な設定です。(詳細は後ほど)
とりあえず現段階では、上記の設定にしておいてください。2 rm -f tmp/pids/server.pidについて
開発途中に何度もdockerコンテナの起動&停止を繰り返していると時々「A server is already running.」のエラーが出ます。(pidファイルが残っていることが原因)
上記コマンドでserver.pidを逐一削除することで未然にエラーを防ぎます。関連記事
docker-compose upしたときに「A server is already running.」って言われないようにする4 database.yml編集
次にconfig/database.ymlの設定をdocker-compose.ymlの内容に合わせます。
具体的には(1) password (2)host:dbの2つを追記してください。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password socket: /tmp/mysql.sock host: db development: <<: *default database: bookapp_development5 コンテナをbuildする docker-compose build
ここまで来たら、次はDocker環境のコンテナを作っていきます。
ターミナルで以下のコマンドを実行してください。
(初めて実行するときは少し時間がかかります)[bookapp] $ docker-compose build6 Dockerコンテナ上でDB作成&migrationを実行
buildが無事完了したら、次はDB作成&migrationです。
ターミナルで以下のコマンドを実行してください。[bookapp] $ docker-compose run web bundle exec rake db:create Created database 'bookapp_development' Created database 'bookapp_test' [bookapp] $ docker-compose run web bundle exec rake db:migrate == CreateBooks: migrating ====================================== -- create_table(:books) -> 0.0150s == CreateBooks: migrated (0.0152s) =============================[解説]
Dockerコンテナはlocalとは環境が異なります。
その為、再度DB作成&migrationの実行が必要になりますdocker-compose run webコマンドについて
Dockerコンテナでのrailsコマンドは通常のコマンドの前に
「docker-compose run web」をつけて実行する必要があります。
通常のlocal環境のように「rake db:migrate」のみでは
変更が反映されないので注意してください。7 コンテナを起動する docker-compose up
ここまで来たら、ブラウザでTopページ表示が可能になります。
まず以下のコマンドでDockerコンテナを立ち上げます。
[bookapp] $ docker-compose up Starting bookapp_db_1 ... done Recreating bookapp_web_1 ... doneコンテナが立ち上がった後、ブラウザでTopページが表示されるか確認してみてください。
http://localhost:3000/8 DB接続設定を環境変数に置き換え - env_file
ここまでの作業でDocker開発環境の構築は完了ですが、
DB接続関連の設定がファイルにベタ打ち(ハードコーディング)になっているのが
気になる方もいらっしゃるかもしれません。Docker開発でも環境変数でDB接続関連の設定を読み込むことが可能です。
具体的には以下3つの作業を実施します。
(1) envファイル(db.env)を作成
(2) database.ymlを編集
(3) docker-compose.ymlを編集1)envファイル(db.env)を作成
Dockerfileと同じルートディレクトリにenvファイルを作成します。
本記事ではファイル名を「db.env」とします。
以下ファイルを追加してください。db.envDB_USERNAME=root DB_PASS=password DB_HOST=db MYSQL_ROOT_PASS=password2)database.ymlを編集
上記で追加した環境変数を読み込む為、config/database.ymlを編集します。
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch('DB_USERNAME', 'root') %> password: <%= ENV.fetch('DB_PASSWORD', 'pass') %> socket: /tmp/mysql.sock host: <%= ENV.fetch('DB_HOST', 'db') %> development: <<: *default database: bookapp_development[解説]
ENV.fetch(第一引数, 第二引数)について
第一引数に「環境変数」、第二引数に「デフォルト値」をセットします。
環境変数がnullの場合は第二引数のデフォルト値が読み込まれます。3)docker-compose.ymlを編集
仕上げにdocker-compose.ymlの設定を変更します。
db、webの2箇所にdb.envを読み込む設定(env_file)を追加してください。docker-compose.ymlversion: '3' services: db: image: mysql:5.7 ports: - "4306:3306" env_file: db.env web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app_name ports: - "3000:3000" depends_on: - db env_file: db.envここまで出来たら一度コンテナを停止&再起動し、
環境変数経由でもTopページが表示できることを確認します。# docker-compose stop or ctrl+Cでコンテナの停止が可能です。 Stopping bookapp_web_1 ... done Stopping bookapp_db_1 ... done #コンテナを再起動 [bookapp] $ docker-compose up Starting bookapp_db_1 ... done Starting bookapp_web_1 ... done9 Docker環境のRailsアプリとSequelProを接続する
それでは最後にDockerコンテナとSequelProを接続してみましょう。
以下のSequelPro接続設定のページを開いてください。
Dockerコンテナ用の接続設定を追加する
設定ページが開けたら以下のようにDockerコンテナ用の接続設定を追加してください。
「名前」・・・任意の名前でOKです。ここでは「docker-sql」としています。
「ホスト」・・・「127.0.0.1」
「ユーザー名/パスワード」・・・envファイルに合わせて記載してください。
「ポート」・・・ 「4306」で設定します。[解説]
ポート:4306について
docker-compose.ymlのポート設定を思い出してください。ports: "4306:3306"
これはホストが4306で接続した時にコンテナ上では3306に置き換えるという意味になります。
localhostのMysqlのデフォルトのポート番号は3306です。
その為ポート番号:3306をそのまま使うと競合が発生して接続エラーが起こります。エラー回避の為、上記のようにDocker環境ではポート:4306を使ってSeqelProに接続する設定にします。
上記設定が完了したら、早速確認してみましょう。
dockerコンテナが起動している状態でSequelProの「接続」をクリックします。無事接続できていれば以下のようにDocker環境でもGUIでテーブル内容を確認できます。
参考記事
Docker 公式Tutorial
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
既存のRailsアプリにDockerを導入する手順
Dockerで利用する環境変数をenv_fileを利用して一元管理する方法
- 投稿日:2020-01-13T22:26:10+09:00
Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL)
はじめに
Dockerの公式Tutorialでは、DB:PostgreSQLで解説されている為、他のDB(例:MySQL)でDockerを導入してみたい初学者にとってはややハードルが高い状況です。
本記事では、途中まで作ったRailsアプリ(DB:MySQL)にDockerを導入する方法を解説します。
Dockerを途中から導入するアプリの例としては、scaffoldで作成した
簡単な蔵書管理アプリ(bookapp)を使用します。
(適宜、ご自身で開発中のアプリに置き換えて頂ければと思います)DBはMySQL5.7で実装を進めます。
併せてDocker環境でもDB内容を視覚的に確認できるように
SequelProとの接続方法も解説します。前提
- macでDockerが既に導入されている
Dockerインストールはこちらの記事が分かりやすいです。
DockerをMacにインストールする- Sequelpro インストール済
Sequelproの解説記事はこちら
データベースの操作がGUIで分かりやすく操作できるSequel Proの最低限の使い方目指すゴール
(1) 既存のRailsアプリにMySQLを使ったDocker環境を構築する
(2) Sequelproと接続し、Docker環境でもGUIでDB内容を確認できるようにする
実装の流れ
1 Dockerfile/docker-compose.ymlを作成
2 Dockerfileを編集
3 docker-compose.ymlを編集
4 database.yml編集
5 コンテナをbuildする docker-compose build
6 Dockerコンテナ上でDB作成&migrationを実行
docker-compose run web bundle exec rake db:create / db:migrate7 コンテナを起動する docker-compose up
8 DB接続設定を環境変数に置き換え - env_file
9 Docker環境のRailsアプリとSequelProを接続する
1 Dockerfile/docker-compose.ymlを作成
まずは既存アプリに必要なファイルを追加します。
具体的には(1)Dockerfile (2) docker-compose.ymlの2ファイルです。アプリのルートディレクトリに以下のように2つのファイルを追加してください。
(ファイルの作成場所に注意してください)bookapp ----|-- app |-- bin |-- config |-- db ・・・・・・ ・・・・・・ |-- Gemfile |-- Gemfile.lock |-- package.json |-- Rakefile |-- README.md |-- Dockerfile #ここに追加 |-- docker-compose.yml #ここに追加[解説]
Gemfile/Gemfile.lockについて
・アプリ開発当初からDockerを導入する記事では、ここでGemfile(railsのみ導入)/Gemfile.lock(空でOK)を作成するように記載されていますが、既に開発中のアプリで作成済かと思いますのでGemfileについては特に何もしなくてOKです。2 Dockerfileを編集
次にDockerfileを以下のように編集します。
DockerfileFROM ruby:2.5 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN gem install bundler RUN bundle install ADD . $APP_ROOT[解説]
RUN gem install bundlerについて
Railsアプリに途中からDockerを導入する場合、Gemfileが既にある関係で上記のコマンド無しで進めようとするとエラーが出るかもしれません。(自分の環境ではBuildした時に以下のようなエラーが出ました)ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 20対策として、DockerfileをBuildする際にbundlerを導入しておきます。
3 docker-compose.ymlを編集
続いて docker-compose.ymlを以下のように編集します。
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 'password' ports: - "4306:3306" web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app_name ports: - "3000:3000" depends_on: - db[解説]
1 db:portsの設定について- "4306:3306"
DockerコンテナとSequelpro接続の為に必要な設定です。(詳細は後ほど)
とりあえず現段階では、上記の設定にしておいてください。2 rm -f tmp/pids/server.pidについて
開発途中に何度もdockerコンテナの起動&停止を繰り返していると時々「A server is already running.」のエラーが出ます。(pidファイルが残っていることが原因)
上記コマンドでserver.pidを逐一削除することで未然にエラーを防ぎます。関連記事
docker-compose upしたときに「A server is already running.」って言われないようにする4 database.yml編集
次にconfig/database.ymlの設定をdocker-compose.ymlの内容に合わせます。
具体的には(1) password (2)host:dbの2つを追記してください。config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password socket: /tmp/mysql.sock host: db development: <<: *default database: bookapp_development5 コンテナをbuildする docker-compose build
ここまで来たら、次はDocker環境のコンテナを作っていきます。
ターミナルで以下のコマンドを実行してください。
(初めて実行するときは少し時間がかかります)[bookapp] $ docker-compose build6 Dockerコンテナ上でDB作成&migrationを実行
buildが無事完了したら、次はDB作成&migrationです。
ターミナルで以下のコマンドを実行してください。[bookapp] $ docker-compose run web bundle exec rake db:create Created database 'bookapp_development' Created database 'bookapp_test' [bookapp] $ docker-compose run web bundle exec rake db:migrate == CreateBooks: migrating ====================================== -- create_table(:books) -> 0.0150s == CreateBooks: migrated (0.0152s) =============================[解説]
Dockerコンテナはlocalとは環境が異なります。
その為、再度DB作成&migrationの実行が必要になりますdocker-compose run webコマンドについて
Dockerコンテナでのrailsコマンドは通常のコマンドの前に
「docker-compose run web」をつけて実行する必要があります。
通常のlocal環境のように「rake db:migrate」のみでは
変更が反映されないので注意してください。7 コンテナを起動する docker-compose up
ここまで来たら、ブラウザでTopページ表示が可能になります。
まず以下のコマンドでDockerコンテナを立ち上げます。
[bookapp] $ docker-compose up Starting bookapp_db_1 ... done Recreating bookapp_web_1 ... doneコンテナが立ち上がった後、ブラウザでTopページが表示されるか確認してみてください。
http://localhost:3000/8 DB接続設定を環境変数に置き換え - env_file
ここまでの作業でDocker開発環境の構築は完了ですが、
DB接続関連の設定がファイルにベタ打ち(ハードコーディング)になっているのが
気になる方もいらっしゃるかもしれません。Docker開発でも環境変数でDB接続関連の設定を読み込むことが可能です。
具体的には以下3つの作業を実施します。
(1) envファイル(db.env)を作成
(2) database.ymlを編集
(3) docker-compose.ymlを編集1)envファイル(db.env)を作成
Dockerfileと同じルートディレクトリにenvファイルを作成します。
本記事ではファイル名を「db.env」とします。
以下ファイルを追加してください。db.envDB_USERNAME=root DB_PASS=password DB_HOST=db MYSQL_ROOT_PASS=password2)database.ymlを編集
上記で追加した環境変数を読み込む為、config/database.ymlを編集します。
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch('DB_USERNAME', 'root') %> password: <%= ENV.fetch('DB_PASSWORD', 'pass') %> socket: /tmp/mysql.sock host: <%= ENV.fetch('DB_HOST', 'db') %> development: <<: *default database: bookapp_development[解説]
ENV.fetch(第一引数, 第二引数)について
第一引数に「環境変数」、第二引数に「デフォルト値」をセットします。
環境変数がnullの場合は第二引数のデフォルト値が読み込まれます。3)docker-compose.ymlを編集
仕上げにdocker-compose.ymlの設定を変更します。
db、webの2箇所にdb.envを読み込む設定(env_file)を追加してください。docker-compose.ymlversion: '3' services: db: image: mysql:5.7 ports: - "4306:3306" env_file: db.env web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app_name ports: - "3000:3000" depends_on: - db env_file: db.envここまで出来たら一度コンテナを停止&再起動し、
環境変数経由でもTopページが表示できることを確認します。# docker-compose stop or ctrl+Cでコンテナの停止が可能です。 Stopping bookapp_web_1 ... done Stopping bookapp_db_1 ... done #コンテナを再起動 [bookapp] $ docker-compose up Starting bookapp_db_1 ... done Starting bookapp_web_1 ... done9 Docker環境のRailsアプリとSequelProを接続する
それでは最後にDockerコンテナとSequelProを接続してみましょう。
以下のSequelPro接続設定のページを開いてください。
Dockerコンテナ用の接続設定を追加する
設定ページが開けたら以下のようにDockerコンテナ用の接続設定を追加してください。
「名前」・・・任意の名前でOKです。ここでは「docker-sql」としています。
「ホスト」・・・「127.0.0.1」
「ユーザー名/パスワード」・・・envファイルに合わせて記載してください。
「ポート」・・・ 「4306」で設定します。[解説]
ポート:4306について
docker-compose.ymlのポート設定を思い出してください。ports: "4306:3306"
これはホストが4306で接続した時にコンテナ上では3306に置き換えるという意味になります。
localhostのMysqlのデフォルトのポート番号は3306です。
その為ポート番号:3306をそのまま使うと競合が発生して接続エラーが起こります。エラー回避の為、上記のようにDocker環境ではポート:4306を使ってSeqelProに接続する設定にします。
上記設定が完了したら、早速確認してみましょう。
dockerコンテナが起動している状態でSequelProの「接続」をクリックします。無事接続できていれば以下のようにDocker環境でもGUIでテーブル内容を確認できます。
参考記事
Docker 公式Tutorial
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
既存のRailsアプリにDockerを導入する手順
Dockerで利用する環境変数をenv_fileを利用して一元管理する方法
- 投稿日:2020-01-13T22:11:41+09:00
Docker Toolboxでvolumesでマウントができない場合の対策
Docker Toolbox とは
Windows環境でDockerを使用する場合、
- Windows 10 Pro あるいは Enterprise -> Docker for Windows
- Windows 10 Home(Docker for Windowsのシステム要件を満たしていないもの)-> Docker Toolbox
と使い分ける必要があります。
本記事は、後者のDocker Toolbox利用者向けの内容になります。docker-compose.xmlのvolumes定義でマウント
Docker for WindowsやDocker for Macの場合は、docker-compose.xmlのvolumesに下記のように記述すると、
ホストのディレクトリをコンテナのディレクトリにマウントすることができます。(ホストのディレクトリ:コンテナのディレクトリ
)。docker-compose.xmlversion: '3' services: db: ~中略~ environment: ~中略~ volumes: - ./initdb:/docker-entrypoint-initdb.d - ./data:/var/lib/data特に、コンテナ側に/docker-entrypoint-initdb.dを指定すると、
ホスト側に格納していおいた.sqlや.iniをコンテナの初回起動時に実行してくれるため、
DB作成直後のテーブル作成やレコード作成に活用することが多いですね。Docker for WindowsやDocker for Macでは、この記述で期待通りマウントが行われますが、
Docker Toolboxでは、期待通りマウントが行われません。例えば、/docker-entrypoint-initdb.d宛に初回起動時に実行されてほしいsqlのフォルダを指定しても、
コンテナの初回起動時のログに下記のように出力され、sql実行がignoringされてしまいます。
正しくマウントが行われていれば、コンテナの/docker-entrypoint-initdb.dディレクトリにsqlなどがコピー・実行されますが、
マウントに失敗するため、/docker-entrypoint-initdb.d配下にファイルが無く、何も実行されないのです。マウントするための対策
原因は、Docker Toolboxでは、マウントできるホスト側のフォルダが限られているためです。
docker-compose.xmlでそのフォルダを指定する必要があります。Docker Toolboxをインストールした際、VirtialBoxもインストールされたと思います。
VirtialBoxで、Default>右クリック>設定>共有フォルダーで、下記の設定が確認できます。
この「c/Users
」が、マウントできるホストのフォルダです。なので、docker-compose.xmlを下記のように書き換え、
docker-compose.xmlvolumes: - /c/Users/initdb:/docker-entrypoint-initdb.dコンテナの再起動を行います。
(必要に応じて、コンテナの削除→起動を行ってください。)
すると、コンテナ起動時のログに、下記のように出力され、sqlが実行されたことが確認できます。
もちろん、コンテナに入ってもDBに入っても、期待通りマウントとsql実行が行われていることが確認できます。最後に
解決のために、いろいろな記事を参考にさせて頂きました。ありがとうございました。
- 投稿日:2020-01-13T22:09:00+09:00
JubatusをDockerで動かす
Dockerを使うと良いこと
- 公式イメージをpullするだけでJubatusを使うことができる。
- 環境を汚さずバージョン違いのミドルウェアで悩まない。
- 要らなくなった時に消すのが楽ちん。
Docker環境
・Docker for Mac
% docker version Client: Docker Engine - Community Version: 19.03.4 API version: 1.40 Go version: go1.12.10 Git commit: 9013bf5 Built: Thu Oct 17 23:44:48 2019 OS/Arch: darwin/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.4 API version: 1.40 (minimum version 1.12) Go version: go1.12.10 Git commit: 9013bf5 Built: Thu Oct 17 23:50:38 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: v1.2.10 GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339 runc: Version: 1.0.0-rc8+dev GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657 docker-init: Version: 0.18.0 GitCommit: fec3683Jubatusサーバのインストール
Jubatusの公式や、Docker Hubを参考にする。
% docker pull jubatus/jubatus Using default tag: latest latest: Pulling from jubatus/jubatus 8284e13a281d: Pull complete 26e1916a9297: Pull complete 4102fc66d4ab: Pull complete 1cf2b01777b2: Pull complete 7f7a2d5e04ed: Pull complete 4cb7073bed3b: Pull complete Digest: sha256:df700aa354604de1ae6b06d169e4203ac883385d803ec80a6e1fc5cf3fba118a Status: Downloaded newer image for jubatus/jubatus:latest docker.io/jubatus/jubatus:latestimageサイズを確認してみる
% docker images jubatus/jubatus REPOSITORY TAG IMAGE ID CREATED SIZE jubatus/jubatus latest 349bb156c215 11 months ago 415MBちゃんとイメージが取れているっぽいので、Jubatusサーバをコンテナ内で起動してみる。今回は分類器 (Classifier) を同梱されている設定ファイルで起動している。
$ docker run --expose 9199 jubatus/jubatus jubaclassifier -f /opt/jubatus/share/jubatus/example/config/classifier/pa.json起動したっぽいので、Ctrl+Cで一旦終了しておく。
2020-01-13 12:29:03,261 1 INFO [server_util.cpp:429] starting jubaclassifier 1.1.1 RPC server at 172.17.0.2:9199 pid : 1 user : root mode : standalone mode timeout : 10 thread : 2 datadir : /tmp logdir : log config : zookeeper : name : interval sec : 16 interval count : 512 zookeeper timeout : 10 interconnect timeout : 10 2020-01-13 12:29:03,325 1 INFO [server_util.cpp:165] load config from local file: /opt/jubatus/share/jubatus/example/config/classifier/pa.json 2020-01-13 12:29:03,325 1 INFO [classifier_serv.cpp:116] config loaded: { "converter" : { "string_filter_types" : {}, "string_filter_rules" : [], "num_filter_types" : {}, "num_filter_rules" : [], "string_types" : {}, "string_rules" : [ { "key" : "*", "type" : "str", "sample_weight" : "bin", "global_weight" : "bin" } ], "num_types" : {}, "num_rules" : [ { "key" : "*", "type" : "num" } ] }, "method" : "PA" } 2020-01-13 12:29:03,326 1 INFO [server_helper.hpp:226] start listening at port 9199 2020-01-13 12:29:03,326 1 INFO [server_helper.hpp:233] jubaclassifier RPC server startupJubatusクライアントのインストール
Jubatusを使ったクライアントアプリケーションは C++, Python, Ruby または Java で記述することができる。今回はPythonで試してみる。
pip3 install jubatus今度は外れ値検知を試してみる
Jubatusの公式を参考にして学習の設定ファイル(anomaly_config.json)とJubatusを使ったクライアントアプリケーション(anomaly.py)を作成する。それぞれのファイルを適当なディレクトリに置く。
設定ファイル(anomaly_config.json)は「/Users/katuemon/Documents/jubatus/conf/」に、クライアントアプリケーション(anomaly.py)は「/Users/katuemon/Documents/jubatus/」に置いた。
anomaly_config.json{ "method" : "lof", "parameter" : { "nearest_neighbor_num" : 10, "reverse_nearest_neighbor_num" : 30, "method" : "euclid_lsh", "parameter" : { "hash_num" : 8, "table_num" : 16, "probe_num" : 64, "bin_width" : 10, "seed" : 1234 } }, "converter" : { "string_filter_types": {}, "string_filter_rules": [], "num_filter_types": {}, "num_filter_rules": [], "string_types": {}, "string_rules": [{"key":"*", "type":"str", "global_weight" : "bin", "sample_weight" : "bin"}], "num_types": {}, "num_rules": [{"key" : "*", "type" : "num"}] } }anomaly.py(Jubatusを使ったクライアントアプリケーション)では、csvから読み込んだデータをJubatusにサーバ与え、外れ値を検出し結果を標準出力に出すようだ。
anomaly.py#!/usr/bin/env python # -*- coding: utf-8 -*- import signal import sys, json from jubatus.anomaly import client from jubatus.common import Datum NAME = "anom_kddcup"; # handle keyboard interruption" def do_exit(sig, stack): print('You pressed Ctrl+C.') print('Stop running the job.') sys.exit(0) if __name__ == '__main__': # 0. set KeyboardInterrupt handler signal.signal(signal.SIGINT, do_exit) # 1. set jubatus server anom = client.Anomaly("127.0.0.1", 9199, NAME) # 2. prepare training data with open('kddcup.data_10_percent.txt', mode='r') as file: for line in file: duration, protocol_type, service, flag, src_bytes, dst_bytes, land, wrong_fragment, urgent, hot, num_failed_logins, logged_in, num_compromised, root_shell, su_attempted, num_root, num_file_creations, num_shells, num_access_files, num_outbound_cmds, is_host_login, is_guest_login, count, srv_count, serror_rate, srv_serror_rate, rerror_rate, srv_rerror_rate, same_srv_rate, diff_srv_rate, srv_diff_host_rate, dst_host_count, dst_host_srv_count, dst_host_same_srv_rate, dst_host_diff_srv_rate, dst_host_same_src_port_rate, dst_host_srv_diff_host_rate, dst_host_serror_rate, dst_host_srv_serror_rate, dst_host_rerror_rate, dst_host_srv_rerror_rate, label = line[:-1].split(",") datum = Datum() for (k, v) in [ ["protocol_type", protocol_type], ["service", service], ["flag", flag], ["land", land], ["logged_in", logged_in], ["is_host_login", is_host_login], ["is_guest_login", is_guest_login], ]: datum.add_string(k, v) for (k, v) in [ ["duration",float(duration)], ["src_bytes", float(src_bytes)], ["dst_bytes", float(dst_bytes)], ["wrong_fragment", float(wrong_fragment)], ["urgent", float(urgent)], ["hot", float(hot)], ["num_failed_logins", float(num_failed_logins)], ["num_compromised", float(num_compromised)], ["root_shell", float(root_shell)], ["su_attempted", float(su_attempted)], ["num_root", float(num_root)], ["num_file_creations", float(num_file_creations)], ["num_shells", float(num_shells)], ["num_access_files", float(num_access_files)], ["num_outbound_cmds",float(num_outbound_cmds)], ["count", float(count)], ["srv_count",float(srv_count)], ["serror_rate", float(serror_rate)], ["srv_serror_rate", float(srv_serror_rate)], ["rerror_rate", float(rerror_rate)], ["srv_rerror_rate",float( srv_rerror_rate)], ["same_srv_rate", float(same_srv_rate)], ["diff_srv_rate", float(diff_srv_rate)], ["srv_diff_host_rate", float(srv_diff_host_rate)], ["dst_host_count",float( dst_host_count)], ["dst_host_srv_count", float(dst_host_srv_count)], ["dst_host_same_srv_rate",float( dst_host_same_srv_rate)], ["dst_host_same_src_port_rate",float( dst_host_same_src_port_rate)], ["dst_host_diff_srv_rate", float(dst_host_diff_srv_rate)], ["dst_host_srv_diff_host_rate",float(dst_host_srv_diff_host_rate)], ["dst_host_serror_rate",float(dst_host_serror_rate)], ["dst_host_srv_serror_rate",float(dst_host_srv_serror_rate)], ["dst_host_rerror_rate",float(dst_host_rerror_rate)], ["dst_host_srv_rerror_rate",float(dst_host_srv_rerror_rate)], ]: datum.add_number(k, v) # 3. train data and update jubatus model ret = anom.add(datum) # 4. output results if (ret.score != float('Inf')) and (ret.score!= 1.0): print (ret, label)サンプルプログラムの実行
データのダウンロード
% cd /Users/katuemon/Documents/jubatus/ % curl -OL http://kdd.ics.uci.edu/databases/kddcup99/kddcup.data_10_percent.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2094k 100 2094k 0 0 1025k 0 0:00:02 0:00:02 --:--:-- 1025k % gunzip kddcup.data_10_percent.gz # kddcup.data_10_percent.txtはanomaly.pyと同じディレクトリに置く。 % mv kddcup.data_10_percent kddcup.data_10_percent.txtjubaanomalyを起動
% docker run -p 9199:9199 -v /Users/katuemon/Documents/jubatus/conf:/tmp/config jubatus/jubatus jubaanomaly -f /tmp/config/anomaly_config.json起動したっぽい
2020-01-13 12:57:16,401 1 INFO [server_util.cpp:429] starting jubaanomaly 1.1.1 RPC server at 172.17.0.2:9199 pid : 1 user : root mode : standalone mode timeout : 10 thread : 2 datadir : /tmp logdir : log config : zookeeper : name : interval sec : 16 interval count : 512 zookeeper timeout : 10 interconnect timeout : 10 2020-01-13 12:57:16,464 1 INFO [server_util.cpp:165] load config from local file: /tmp/config/anomaly_config.json 2020-01-13 12:57:16,469 1 INFO [anomaly_serv.cpp:140] config loaded: { "method" : "lof", "parameter" : { "nearest_neighbor_num" : 10, "reverse_nearest_neighbor_num" : 30, "method" : "euclid_lsh", "parameter" : { "hash_num" : 8, "table_num" : 16, "probe_num" : 64, "bin_width" : 10, "seed" : 1234 } }, "converter" : { "string_filter_types": {}, "string_filter_rules": [], "num_filter_types": {}, "num_filter_rules": [], "string_types": {}, "string_rules": [{"key":"*", "type":"str", "global_weight" : "bin", "sample_weight" : "bin"}], "num_types": {}, "num_rules": [{"key" : "*", "type" : "num"}] } } 2020-01-13 12:57:16,470 1 INFO [server_helper.hpp:226] start listening at port 9199 2020-01-13 12:57:16,470 1 INFO [server_helper.hpp:233] jubaanomaly RPC server startupJubatus Clientを実行
% python3 anomaly.py実行結果
ちゃんとスコアも表示されていて問題なさそうだ。
id_with_score{id: 190, score: 0.9999999999999419} normal. id_with_score{id: 195, score: 1.0000313006855042} normal. id_with_score{id: 308, score: 0.9999999999986386} normal. id_with_score{id: 476, score: 0.9999999999999977} normal. id_with_score{id: 485, score: 0.9999999999999997} normal. id_with_score{id: 490, score: 0.9999999999999836} normal. id_with_score{id: 495, score: 1.459560361462793} normal. id_with_score{id: 498, score: 0.999999999999998} normal. id_with_score{id: 642, score: 0.9999999999999577} normal. id_with_score{id: 643, score: 0.999999999999923} normal. id_with_score{id: 654, score: 0.9999999999999812} normal. id_with_score{id: 657, score: 0.9999999999999506} normal. id_with_score{id: 683, score: 0.9999999999999567} normal. id_with_score{id: 696, score: 0.9999999999899615} normal. id_with_score{id: 697, score: 0.999999999999993} normal. id_with_score{id: 698, score: 0.9999999999996164} normal. id_with_score{id: 704, score: 0.9999999999999855} normal. id_with_score{id: 705, score: 0.9999999999999941} normal. id_with_score{id: 711, score: 0.9999999999994069} normal. id_with_score{id: 717, score: 0.9999999999999987} normal. id_with_score{id: 835, score: 0.9999999999999999} normal. id_with_score{id: 1123, score: 0.9999999999999933} normal. id_with_score{id: 1128, score: 1.0641230203490077} normal. id_with_score{id: 1129, score: 0.9999999999999983} normal. id_with_score{id: 1149, score: 1.0401485737893268} normal. id_with_score{id: 1150, score: 0.9999999999996244} normal. id_with_score{id: 1166, score: 0.9999999999999964} normal. id_with_score{id: 1173, score: 0.9999999999999908} normal. id_with_score{id: 1175, score: 0.999999999999569} normal. id_with_score{id: 1662, score: 0.9999999999999974} normal. id_with_score{id: 1678, score: 0.9999999999999984} normal. id_with_score{id: 1681, score: 0.9999999999999952} normal. id_with_score{id: 1692, score: 0.9999999999992727} normal. id_with_score{id: 1710, score: 1.2717678419359209} normal. id_with_score{id: 1711, score: 0.9999999999999989} normal. id_with_score{id: 1720, score: 0.9999999999999992} normal. id_with_score{id: 1732, score: 0.9999999999999959} normal. id_with_score{id: 1733, score: 0.9999999999999953} normal. id_with_score{id: 1745, score: 0.9999999999999792} normal. id_with_score{id: 1746, score: 0.9999999999999954} normal. id_with_score{id: 1881, score: 0.9999999999999983} normal. id_with_score{id: 2212, score: 0.9999999999999997} normal. id_with_score{id: 2285, score: 0.9999999999999966} normal. id_with_score{id: 2287, score: 0.9999999999999962} normal. id_with_score{id: 2288, score: 0.9999999999999981} normal. id_with_score{id: 2292, score: 1.388673102986125} normal. id_with_score{id: 2337, score: 0.9999999999999991} normal. id_with_score{id: 2347, score: 0.999999999999961} normal. id_with_score{id: 2355, score: 0.9999999999999994} normal. id_with_score{id: 2358, score: 1.0560593847508284} normal. id_with_score{id: 2383, score: 0.9994229663750211} normal. id_with_score{id: 2394, score: 0.9999999999999631} normal. id_with_score{id: 2463, score: 0.9999999999999991} normal. id_with_score{id: 2500, score: 0.7581637350497832} normal. id_with_score{id: 2501, score: 0.0} normal. id_with_score{id: 2509, score: 0.9999999999999997} normal. id_with_score{id: 2529, score: 0.999999999999994} normal. id_with_score{id: 2548, score: 0.9999999936138952} normal. 以降省略
- 投稿日:2020-01-13T20:41:02+09:00
Dockerfileチートシートv19.03
概要
Dockerfile
命令をまとめました。
※すべて網羅しているわけではありません。
- Dockerバージョン:v19.03
早見表
命令 概要 FROM ベースイメージを指定 RUN 指定コマンドを実行 ENTRYPOINT コンテナ実行時のコマンドを指定 CMD コンテナ実行時のコマンドを指定(上書き可) COPY ホストマシンからコンテナイメージへファイル/ディレクトリを単純コピー ADD COPY+解凍 / URLからダウンロード(非推奨) ENV 環境変数を追加 EXPOSE 指定ポートを開ける WORKDIR カレントディレクトリを変更 MAINTAINER deprecated
現在はLABEL maintainer="maintainer@example.com"
と指定すべきFROM
ベースイメージを指定して取得する。1行目はこのコマンド。
Docker HubやプライベートDockerリポジトリからイメージを指定する。
- タグ指定なし :
latest
でpullDockerfileFROM centos:7.7.1908 ENTRYPOINT [ "/bin/bash" ]$ docker image build --file Dockerfile -t testrepo:test . $ docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" REPOSITORY TAG SIZE testrepo test 204MB # <- 今回作成されたイメージ centos 7.7.1908 204MB # <- ベースイメージ
- タグ指定あり :指定したタグでpull、基本的には指定すべき
DockerfileFROM centos ENTRYPOINT ["/bin/bash"]$ docker image build --file Dockerfile -t testrepo:test . $ docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" REPOSITORY TAG SIZE testrepo test 220MB # <- 今回作成されたイメージ centos latest 220MB # <- ベースイメージRUN
指定したコマンドを実行する。
Tipsで後述していますが、できる限りまとめて記載すべきです。DockerfileFROM centos:8 RUN yum install -y httpd iproute && \ echo "Test" > /var/www/html/index.html ENTRYPOINT ["/usr/sbin/httpd"]ENTRYPOINT / CMD
コンテナを起動した際に実行されるコマンドを規定する。
Dockerfileの最後の方にそれぞれ1回ずつ記述されるイメージ。DockerfileFROM centos:8 ENTRYPOINT ["df"] CMD ["-H", "."]指定するコマンドについては、半角スペースごとにコンマで区切って記述します。例えば
go run main.go
を指定したい場合は以下のようになります。DockerfileENTRYPOINT ["go", "run", "main.go"]ENTRYPOINTとCMDの違い
ENTRYPOINT
は必ずそのまま実行される、
CMD
はコンテナ起動時に引数を指定すると上書き可能です。
この特性を生かして、ENTRYPOINT
にはコマンド部分、CMD
にはコマンドのオプション部分を記載するという書き方ができます。e.g.
上記のDockerfileでイメージをビルドしたとして、引数ありでコンテナ起動する場合と引数なしでコンテナ起動する場合の、具体的な実行結果の違いを以下に示しておきます。
- 引数なし :
df -H .
が実行される。Terminal$ docker run override_test Filesystem Size Used Avail Use% Mounted on overlay 63G 2.1G 58G 4% /
- 引数あり(
--help
) :df --help
が実行される。CMDの部分がコマンドラインに指定したオプションで上書きされていることがわかる。Terminal$ docker run docker_test --help Usage: df [OPTION]... [FILE]... Show information about the file system on which each FILE resides, or all file systems by default. Mandatory arguments to long options are mandatory for short options too. -a, --all include pseudo, duplicate, inaccessible file systems # 以下略COPY
ホストマシンのファイルまたはディレクトリをコンテナ内にコピーする。
ホストマシンのディレクトリ構成. ├── Dockerfile ├── copydir │ ├── subfile.txt │ └── subfile2.txt └── copyfile.txt
DockerfileFROM centos:latest RUN mkdir -p /app # ファイル単位 COPY ./copyfile.txt /app # ディレクトリ単位 ※コピー先のディレクトリ名称を指定する必要あり COPY ./copydir /app/subdirTerminal$ docker image build --file Dockerfile -t centos:copytest . $ docker container run centos:copytest ls -ltR /app /app: total 8 drwxr-xr-x 2 root root 4096 Jan 12 13:27 subdir -rw-r--r-- 1 root root 6 Jan 12 13:18 copyfile.txt /app/subdir: total 4 -rw-r--r-- 1 root root 0 Jan 12 13:25 subfile2.txt -rw-r--r-- 1 root root 8 Jan 12 13:19 subfile.txtADD
COPY
同様、ホストマシンのファイルをコンテナ内へコピーします。(上記DockerfileのCOPY
をADD
としても同様の結果となります)
ただし、ADD
を利用すると、コピーファイルが圧縮ファイルであった場合、自動的に解凍が行われます。DockerfileFROM centos:8 RUN mkdir -p /app # addtest.tarはcopydirを圧縮したファイル ADD ./addtest.tar /appTerminal$ docker image build --file add/Dockerfile -t centos:addtest . $ docker container run centos:addtest ls -lR /app /app: total 4 drwxr-xr-x 2 501 games 4096 Jan 12 13:25 copydir /app/copydir: total 4 -rw-r--r-- 1 501 games 8 Jan 12 13:19 subfile.txt -rw-r--r-- 1 501 games 0 Jan 12 13:25 subfile2.txt # tarファイルそのものはなく、解凍されたものが存在しているe.f.
COPY
の場合Terminal$ docker container run testrepo:copy ls -lR /app /app: total 4 -rw-r--r-- 1 root root 177 Jan 12 13:31 addtest.tar # 圧縮ファイルはそのままCOPYとの棲み分け
- 単純にコピーのみ行う場合 => COPY
- コピーして解凍も行う場合 => ADD
ENV
実行コンテナに環境変数を追加する。
DockerfileFROM centos:8 ENV NEW_ENV='env-value' ENTRYPOINT ["/bin/bash"]Terminal$ docker image build --file Dockerfile -t centos:envtest . $ docker container run -it centos:envtest [root@de6dc0f2577e /]# env LANG=en_US.UTF-8 HOSTNAME=de6dc0f2577e NEW_ENV=env-value # fileで追加した環境変数EXPOSE
指定したポートをLISTEN状態とする。
DockerfileFROM python:3.8-slim-buster COPY . / RUN pip install -r /app/requirements.txt EXPOSE 9876 ENTRYPOINT [ "gunicorn", "flask_app:app" ] CMD [ "-c", "/app/config/gunicorn_settings.py" ]Terminal$ docker image build --file Dockerfile -t python:flask . $ docker container run --name exposetest python:flask $ docker container ps --format "table {{.Image}}\t{{.Ports}}" IMAGE PORTS NAMES flask 9876/tcp exposetest a2556b3a812 worktest # PORTSにLISTENEDのポート番号が表示されるWORKDIR
作業ディレクトリを変更する。
Dockerfileにおけるcd
DockerfileFROM centos:8 RUN mkdir -p /test RUN echo "before workdir" > before.txt WORKDIR /test RUN echo "after workdir" > after.txt ENTRYPOINT ["/bin/bash"]Terminal$ docker image build --file Dockerfile -t centos:workdir . $ docker container run -it --name workdirtest centos:workdir [root@4cd5accab742 test]# pwd /test # 最後にWORKDIRしたパスがコンテナ起動時のベースパスとなる [root@4cd5accab742 test]# ls .. | grep before before.txt # beforeはWORKDIR前に作成したのでルートディレクトリに存在 [root@4cd5accab742 test]# ls . after.txt # afterはWORKDIR後に作成したため/testディレクトリに存在Tips
マルチステージビルドで軽量化
マルチステージビルドとは、最終完成イメージの軽量化をはかるためのテクニックのようなものです。
一例としてGo
アプリケーションをマルチステージビルドを適用した場合とどうでない場合とのイメージサイズを比較してみます。マルチステージビルドなし
DockerfileFROM golang:1.13-alpine COPY ./main.go ./ RUN go build -o /app ./main.go ENTRYPOINT ["/app"]マルチステージビルドあり
Dockerfile# 1段階め処理(ビルドを行う) FROM golang:1.13-alpine as builder COPY ./main.go ./ RUN go build -o /app ./main.go # 2段階め(これが完成イメージ) # ベースに最低限の要件を満たす軽量イメージを選択する FROM alpine:3.11 # 1段階めのコンテナから必要物をコピー COPY --from=builder /app . ENTRYPOINT ["./app"]サイズ確認
TerminalREPOSITORY TAG SIZE golang nonmultistage 361MB golang multistage 7.6MB # 圧倒的に軽い
命令はできるだけまとめる
Dockerfileの命令1つにつき、イメージキャッシュがそれぞれ構築されていきます。このイメージキャッシュレイヤー数を最小化することはベストプラクティスとされており、それにより軽量化やビルド高速化につながることがあります。具体的には以下の処置を行います。
- RUN命令は「バックスラッシュ + &&」でつなげて記載
- COPY/ADD命令もできるだけ少なくまとめる
特に
RUN
に関してはビルド時キャッシュの問題もあって、yum update
とyum install
は&&
でつなげて書かないとワナにはまる等といったことがあります。(キャッシュについては本記事では記載しません。)ADD命令にはURLも指定可能でもそうは書かない
レイヤ数を少なくし、イメージ軽量化につなげるために、URLからリソースを取得する場合は
curl
またはwget
を利用します。Example(Best practicesより抜粋)
Dockerfile# Good RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all # Bad ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things allReference
- 投稿日:2020-01-13T20:10:12+09:00
【docker-compose】Nuxt.jsとGOでREST APIを構築した手順
現在のプロジェクトでは、Nuxt.jsとGOをGKEを使ってREST APIを構築しています。
Kubernetesを利用する場合、Dockerを必然的に利用することになる上に、ローカルの環境構築も色々面倒だったため、
docker-compose
を利用することにしました。今回は、
Nuxt.js
とGO
でdocker-compose
を利用して、REST APIを構築したため、その手順をまとめています。docker-composeで構築したNuxt.jsとGOのREST APIの雑な全体像
表題の通り、めっちゃ雑なポンチ絵を作りました。
以上のようなデータの流れを再現するための手順をまとめます。
docker-composeでNuxt.jsとGOでREST APIを構築した手順の目次
ざっくりとした手順は以下です。
- ディレクトリ構成※
- Dockerfileを作成
- my.cnfを作成
- docker-compose.ymlを作成
- docker-compose upを実行
- アプリ側やコンテナ等の細かい設定変更
これから手順を詳細にまとめていきます。
※ディレクトリ構成は手順ではないですが目次には入れておきます。
ディレクトリ構成
※
Nuxt.js
とGO
でリポジトリが分かれている前提で手順を進めます。Nuxt.js側のディレクトリ構成
ディレクトリ構成も雑に最低限共有します。
project_name ├──app/ ├──nuxt.config.js ├──package.json ├──Dockerfile #本番用 ├──Dockerfile.dev └──docker-compose.yml※app/は作業しているファイルが格納されているディレクトリ(ディレクトリ構成については以下にまとめました。)
参考:https://qiita.com/arthur_foreign/items/637f2976e9f5e7a89727
GO側のディレクトリ構成
project_name ├──app/ │ ├──main.go │ └──database.go ├──config/ #tomlで見にいくDBを出し分け(現プロジェクトでは廃止予定ですが備忘録のため記載) │ ├──config_dev.toml │ ├──config_stg.toml │ ├──config_prd.toml │ └──config_docker.toml ├──db/ │ ├──migrations/ #マイグレーションファイル │ ├──mysql/ │ │ └──my.cnf # MySQLの設定ファイル │ └──seed/ #シードデータ ├──Dockerfile #本番用 ├──Dockerfile.dev └──docker-compose.yml※appは作業ディレクトリ
Dockerfileを作成
まずは、
Nuxt.js
とGO
のDockerfile
を作成しました。※本番環境とローカル環境で利用するDockerfileを分けるため、別名のDockerfileを用意しています。
※現プロジェクトのソースコードほぼコピペです。
Nuxt.js
Dockerfile.devFROM node:10.15.1-alpine as dev WORKDIR /app COPY . /app RUN apk update && \ apk add git RUN yarn CMD ["yarn", "dev"]GO
Dockerfile.devFROM golang:1.12-alpine as builder ADD . /go/src/github.com/project_name WORKDIR /go/src/github.com/project_name ENV GO111MODULE=on RUN apk update && \ apk add git && \ go get github.com/jmoiron/sqlx && \ GOOS=linux GOARCH=amd64 go build main.go FROM alpine:3.9 WORKDIR /app COPY --from=0 /go/src/github.com/project_name /app CMD ["./main"]multi-stage buildが出来るDockerfileの作成方法は、以下の記事に詳細をまとめましたので、参考にしていただけますと幸いです。
参考:https://qiita.com/arthur_foreign/items/fca369c1d9bde1701e38
my.cnfを作成
my.cnf[mysqld] character-set-server=utf8mb4 default_storage_engine=InnoDB log-error=/var/log/mysql/mysqld.log log_timestamps=SYSTEM slow_query_log=ON slow_query_log_file=/var/log/mysql/slow_query.log [client] default-character-set=utf8mb4 [mysql] default-character-set=utf8mb4 [mysqldump] default-character-set=utf8mb4
my.cnf
についてはこの記事では触れません。以下の記事を参考にしました。
参考1:https://qiita.com/at_1016/items/874788e375938837a027
参考2:https://blog.apar.jp/linux/6769/
参考3:https://qiita.com/YusukeHigaki/items/2cab311d2a559a543e3a
参考4:https://qiita.com/EigenPort_M/items/46bd1bf386eb56eba97edocker-compose.ymlを作成
Nuxt.js
docker-compose.ymlversion: '3' services: front: build: context: . dockerfile: "Dockerfile.dev" volumes: - ./:/project_name/app ports: - "3000:3000" tty: true stdin_open: true networks: - project_name_default networks: project_name_default: external: trueGO
docker-compose.ymlversion: '3' services: api: build: context: . dockerfile: "Dockerfile.dev" volumes: - ./:/go/src/github.com/project_name ports: - "8080:8080" environment: - APP_ENV=docker depends_on: - db tty: true stdin_open: true networks: - project_name_default db: image: mysql:5.7 restart: always volumes: - ./db/mysql/my.cnf:/etc/mysql/conf.d/my.cnf environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: project_name MYSQL_USER: root ports: - "3306:3306" networks: project_name_default: external: true
networks
には、docker network ls
で出たネットワークを指定しました。(先にdocker-compose up
しています。)
docker-compose.yml
については、以下のドキュメントを参考にしました。参考1:https://qiita.com/sanoyo/items/294e76cbcb80df35a32f
参考2:https://qiita.com/at-946/items/08de3c9d7611f62b1894docker-compose upを実行
Nuxt.js
側とGO
側で以下のコマンドを実行しましょう。$ docker-compose up
docker-compose up --build
等のオプションは、以下のドキュメントが参考になりました。参考:http://docs.docker.jp/compose/reference/up.html
うまくいかない時はアプリやコンテナの設定を変えたり、DBにデータをつっこんだりしましょう。
アプリ側やコンテナ等の細かい設定変更
以下は、プロジェクトで僕がやった手順をまとめただけです。
- Nuxt.jsでリクエストするエンドポイントを指定
- GO側で見にいくDBを指定(toml管理)
- コンテナのlocale設定
※人によって手順が違うところもあると思うので、容赦無く読み飛ばしてもらえると嬉しいです。
Nuxt.jsでリクエストするエンドポイントを指定
エンドポイントの指定方法だけ書きます。
index.vue$axios.$get('http://api:8080')先ほど、
docker-compose.yml
に同じnetworks
をNuxt.js
側とGO
側に設定しています。
docker-compose.yml
に書いたAPI側のサービス名「api」を、エンドポイントのホスト名に指定することで、API側にリクエストを届けることが出来ました。また、エンドポイントのホスト名を
localhost
に指定すると、connect ECONNREFUSED 127.0.0.1:8080
のエラーが発生しました。GO側で見にいくDBを指定(toml管理)
docker-compose.yml
で設定した環境変数「APP_ENV=docker」によって、config_docker.toml
の設定を見にいくようにしています。見にいくDBをtomlで管理しました。
config_docker.toml[MySQL] Host = "db" Username = "root" Database = "project_name" Password = "password"database.goimport ( "os" "github.com/BurntSushi/toml" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var DB *sqlx.DB type Config struct { MySQL MySQLConfig } type MySQLConfig struct { Username string Password string Host string Database string } func init() { var config Config var configFilePath string basePath := os.Getenv("APP_PATH") env := os.Getenv("APP_ENV") switch env { case "production": configFilePath = basePath + "config/config_prd.toml" case "staging": configFilePath = basePath + "config/config_stg.toml" case "development": configFilePath = basePath + "config/config_dev.toml" case "test": configFilePath = basePath + "config/config_test.toml" case "docker": configFilePath = basePath + "config/config_docker.toml" default: configFilePath = basePath + "config/config_dev.toml" } _, err := toml.DecodeFile(configFilePath, &config) if err != nil { panic(err.Error()) } dbConfig := config.MySQL.Username + ":" + config.MySQL.Password + "@tcp(" + config.MySQL.Host + ":3306)/" + config.MySQL.Database + "?parseTime=true" db, err := sqlx.Connect("mysql", dbConfig) if err != nil { panic(err.Error()) } DB = db }コンテナのlocale設定
手順は以下の記事を参考にしました。
参考:https://qiita.com/maejima_f/items/4d5432aa471a9deaea7f
現プロジェクトでは中国語の繁体字を利用しているため、その設定をそのまま記事に書きます。
まず、MySQLのコンテナ名を出します。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES xxxxxxxxxxxx mysql:5.7 "docker-entrypoint.s…" 25 hours ago Up 12 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp project_name _db_1次に、Dockerコンテナの中のシェルに入りましょう。
$ docker exec -it project_name_db_1 bash中国語繁体字(
zh_TW
)のlocaleを追加します。$ apt-get update $ apt-get install locales locales-all $ dpkg-reconfigure locales $ export LANG=zh_TW.UTF-8追加されたlocaleを確認してみましょう。
$ locale LANG=zh_TW LANGUAGE= LC_CTYPE="zh_TW" LC_NUMERIC="zh_TW" LC_TIME="zh_TW" LC_COLLATE="zh_TW" LC_MONETARY="zh_TW" LC_MESSAGES="zh_TW" LC_PAPER="zh_TW" LC_NAME="zh_TW" LC_ADDRESS="zh_TW" LC_TELEPHONE="zh_TW" LC_MEASUREMENT="zh_TW" LC_IDENTIFICATION="zh_TW" LC_ALL=INSERT文で適当なクエリを投げてちゃんと入力できていたら完了です。
- 投稿日:2020-01-13T19:35:08+09:00
【初学者向け】セキュリティ対策入門①〜XSS編〜
前提
確認環境
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本シリーズの目的
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本記事の目標
XSS(クロスサイトスクリプティング)の概要、原因、対策について理解することです。
本記事を読み進める上での必要事項
以下の内容を終えていることです。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜概要
XSS(クロスサイトスクリプティング)とは
不正なスクリプトを混入・実行させることです。掲示板などエンドユーザからの入力値をWebページに表示させる機能を持ったWebアプリケーション全般で行われる可能性があります。
これだけだとわかりにくいと思いますので実際に見てみましょう。
実際に体験してみよう
本シリーズで使うコンテナを起動して
http://localhost:8080/xss/bad.php?name=J
にアクセスしてみましょう。『こんにちは、Jさん』とブラウザに表示されていると思います。これは
name
パラメータの内容をもとに挨拶文言を表示させているものです。ソースコードはtutorial-php-security/www/html/xss/bad.php
にあります。tutorial-php-security/www/html/xss/bad.php<?php $name = $_GET['name']; ?> <p>こんにちは、<?= $name; ?>さん</p>
$_GET['name']
でクエリストリングスのname
パラメータを参照しています。これが上述するところの『エンドユーザからの入力値』の一例です。クエリストリングスだけではなく、POST送信で渡ってきたデータやDBに格納されたデータを出力する場合も、エンドユーザの入力によるものであれば同様です。次に
http://localhost:8080/xss/bad.php?name=<script>alert(1)</script>
にアクセスしてみましょう。以下2点が重要です。
- ダイアログが表示
- ダイアログで『OK』をクリックしたのち『こんにちは、さん』と表示
何が起きているかというと
name
パラメータの<script>alert(1)</script>
がスクリプトと見なされてJavaScriptのコードが実行されてしまっているのです。alert()
はダイアログを表示させるJavaScriptの関数です。今回は単にダイアログが表示されただけなので大したことがないと思うかもしれません。しかし、別のサーバにリダイレクトさせられたりCookie情報を盗まれてしまったり様々な被害が生じてしまいます。
原因
不正なスクリプトを表示させていることです。これを避けるにあたって様々な対策があります。その一部を以下で具体的に紹介していきます。
対策
エスケープする
以下にアクセスしてみましょう。
http://localhost:8080/xss/good.php?name=<script>alert(1)</script>
bad.php
ではなくgood.php
にアクセスしていることに注意しましょう。実際にアクセスすると『ダイアログが表示されない』『「こんにちは、<script>alert(1)</script>さん」と表示されている』ことがわかると思います。
good.php
ではある処理を追加しています。それを次に確認しましょう。tutorial-php-security/www/html/xss/good.php<?php $name = $_GET['name']; ?> <p>こんにちは、<?= htmlspecialchars($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'); ?>さん</p>ポイントは
htmlspecialchars()
の部分です。これはエスケープするための関数です。エスケープとは<
や>
などHTMLにおいて特殊な意味を持つ文字列をそれぞれ<
や>
など安全な文字列に置き換えることです。詳細は『文字参照』などで検索してみてください。<
は画面上では<
と表示されるため『こんにちは、<script>alert(1)</script>さん』と表示されているわけです。そもそも出力しない
エスケープが機能しない場合もあります。たとえば
<script><?= $js_code; ?></script> <a href=<?= $link_url; ?>>危ないリンク</a>というケースを考えてみましょう。
前者では
alert(2)
という文字列が$js_code
に格納されている場合、エスケープしたところで意味がありません。bad.php
と同様ダイアログが表示してしまいます。後者では
JavaScript:alert(3)
という文字列が$link_url
に格納されている場合、エスケープしたところで意味がありません。bad.php
と同様ダイアログが表示してしまいます。ちなみにJavaScript:XXX
のような記述をJavaScript擬似プロトコルといいます。JavaScript擬似プロトコルで記述できる箇所ではエスケープが意味を成しません。こういう場合では、ユーザが入力したデータをそのまま出力しない仕様にするのが得策です。
入力チェックを行う
ユーザの入力をDBに格納するときやユーザの入力データを出力する際に、入力チェックを行うことも有効です。極端な例ですが、
name
パラメータに1
と2
という文字列以外入力を認めないようにバックエンドでチェックすればスクリプトは混入しなくなります。ちなみに、フロントエンドでの入力チェックやHTMLタグの属性などエンドユーザ側で書き換えできてしまうチェック方法は意味がないので気をつけてください。ライブラリを使う
ブログアプリケーションを作成するときのように、ユーザにタグ入力を認める機能を実装する場合は、ユーザの入力データからスクリプトだけを上手く取り除くロジックが必要です。それを0から作るのはあまり現実的ではないためライブラリを導入ことが検討に値する解決策となります。
一例ですがHTML Purifierというものがあります。
参考文献
今回の内容は以上です。最後までご覧いただきありがとうございました。
- 投稿日:2020-01-13T18:28:18+09:00
Dockerで立ち上げたDjangoの内蔵サーバに同LAN上の別PCからアクセス
背景
LAN内PC上のDjangoサイト(Django内蔵サーバ)に同LAN内別PCからアクセスしたい。
環境
・Windows 10 Enterprise
・Docker for Windows 2.1.0.1
・Django 3.0(Dockerコンテナ)
・MySQL 5.7(Dockerコンテナ)
・プロキシあり参考
こちらの記事の内容で行けました↓
Docker for Windowsのコンテナに同一ネットワークのスマホからアクセスファイアウォール設定が阻んでいた様です。
ポートフォワードはdocker-composeファイルで設定しているので無視。
上記設定後、サイトにアクセスするとエラー。
「Invalid HTTP_HOST header~」これを調べると、setting.py中の「ALLOWED_HOSTS」が空白になっている事が原因だった。
「= ['*']」に変更する事でアクセス出来た。
- 投稿日:2020-01-13T17:12:38+09:00
コンテナの起動とログインについて調べてみたメモ
はじめに
前回はdockerfileを作成してイメージ作成からapacheの起動までをやってみました。
その際いろいろ調べていて、起動やコンテナへのアクセスで不明点が結構あったので詳しく調べてみた内容をまとめたいと思います。docker runしたのに起動しない!?
ubuntuのイメージを使ってみます。
イメージ名を指定してrunします。$ docker run ubuntu$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES確認してみると、起動していません。
あれ?
-aオプションですべてのコンテナを確認してみます。
ありますね...$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d1d595f53e6b ubuntu "/bin/bash" 14 seconds ago Exited (0) 13 seconds ago funny_wozniakSTATUS欄がExted(0)...???
docker startしてみます。$ docker start d1d595f53e6b d1d595f53e6b $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESう・ご・か・な・い!!
動かない
なぜでしょうか。docker run -it
いろいろ調べてみると、runするタイミングで-itオプションを付与していないのが原因らしい。
なにこれ。$ docker run -d -it ubuntu 4af17d141f3e5f037539a49ee367785abda784d8cff501644820de7d1fdb9009 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4af17d141f3e ubuntu "/bin/bash" 4 seconds ago Up 4 seconds boring_hamilton-i →標準入力を開いたままにする
-t →擬似ttyに接続。
-dはコンテナを作成後、コンテナプロセスの標準入出力から抜けておく感じのオプション。なのでいったん置いときます。これをつけると問題なく起動して停止、起動も問題なくできました。
ログイン方法で挙動が違う。attachとexec
以下の記事を参考にさせていただきました。
@RyoMa_0923さん
Dockerコンテナ内で操作 attachとexecの違いdocker attach
コンテナ上で起動しているPID=1の標準入出力(STDIN/STDOUT)に接続するらしい。
attachはコンテナで立ち上がってるPID=1の標準入出力に接続する。
よってexitするとコンテナもろとも停止する模様。(恐ろしいですね)docker exec
docker exec はコンテナ内で新たなプロセスを実行する。
よってexitを実行してもコンテナ自体は停止しないみたいです。いつもの癖でexitで抜ける人はexecを使うほうがいいかもしれないなって思いました。
前回記事ではまったコンテナ起動してもapachのテストページが表示されな問題
dockerfileから作成したapacheのイメージを使用してコンテナを作成して起動します。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE apache latest b5f41134a3a1 5 minutes ago 599MB ubuntu latest 549b9b86cb8d 3 weeks ago 64.2MB $ docker run -d -p 80:80 apache /sbin/init 1fdab23f462e139188cf552d17ae88d8c4137f12e9ab9b4d50393264ec225ce8 $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1fdab23f462e apache "/sbin/init" 9 seconds ago Up 9 seconds 0.0.0.0:80->80/tcp dazzling_pareちゃんと起動してますね。
実際にアクセスしてみますが、このサイトにアクセスできません。と例の悲しい表示がでます。調べてでてきた謎のオプションを付与して再度コンテナを作成、起動します。
$ docker run --privileged -d -p 80:80 apache /sbin/init 8d83edca0037bf9a11c097084e2940a92dc3c8473d5c99c7a5e5faf1439bdbdb $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8d83edca0037 apache "/sbin/init" 6 seconds ago Up 6 seconds 0.0.0.0:80->80/tcp admiring_varahamihira起動までは問題なくできました。
ブラウザからアクセスしてみると、テストページが表示されました!!
変わったことといえば謎の「--privileged」これなに?
調べてみました。docker run --privileged
dockerコンテナはデフォルトでは権限がないため、コンテナ中のデーモンを動かすことができないみたいです。
そこで使用するのが--privileged
これを使うとdockerはホスト上のすべてのデバイスに対して接続可能となるようです。まとめ
- docker run して起動せず、docker startもできない場合はオプション問題なのかも。
- attachとexecは同じログインでも挙動が全く別物
- コンテナはデフォルトではデーモンを動かすことができない。
以上です。調べながらもやや不明な点があったりもしたので間違ってるところなどあったらご指摘いただけると幸いです。
- 投稿日:2020-01-13T16:11:40+09:00
Dockerコンテナにパッケージ管理ツールpoetryを使って分析環境を構築する方法
昨年のとあるアドベントカレンダーでPythonのパッケージ管理ツールの一つ
poetry
が紹介されていました。
2020 年の Python パッケージ管理ベストプラクティス
普段業務で使っているのはpipenv
なのですが、パッケージを作るのにsetup.py
とかその他諸々のファイルが必要で面倒だったり、pipfile.lock
の作成に異常に時間がかかったりしていてストレスを感じていましたが、poetry
だとそられがいい具合に解決されている?とのことだったので試してみました。
poetry
を使ってDocker
コンテナに分析環境を構築したので、その方法をメモします。
コードはこちらにあげています。解説
ディレクトリ構造は以下の通りです。
tree. ├── Dockerfile ├── README.md ├── docker-compose.yml ├── notebook # EDAで使用するnotebook │ └── eda.ipynb ├── poetry.lock ├── pyproject.toml ├── src # パッケージ化したいモジュール │ ├── __init__.py │ └── helloworld.py └── tests # テストコード ├── __init__.py └── test_helloworld.py設定ファイル群
Dockerfile
、docker-compose.yml
、pyproject.toml
は以下の通りです。pyproject.toml
pandas
、numpy
、sklearn
、matplotlib
といったパッケージを入れています。
また、開発用にpytest
も入れています。
※他パッケージの追加方法に関しては後述pyproject.toml[tool.poetry] name = "poetry_docker" version = "0.1.0" description = "" authors = ["yolo-kiyoshi"] [tool.poetry.dependencies] python = "^3.7" sklearn = "^0.0" jupyterlab = "*" pandas = "*" numpy = "*" matplotlib = "*" seaborn = "*" japanize-matplotlib = "*" [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"Dockerfile
poetry
はデフォルトで仮想環境venv
を構築しますが、Dockerコンテナにわざわざvenv
を構築する必要はないので、poetry config virtualenvs.create false
によってコンテナ上に直にパッケージをインストールするようにしてます。
その後、poetry install
によってpoetry.lock
の依存関係をみながらパッケージをインストールします。DockerfileFROM python:3.7-slim ENV PYTHONUNBUFFERED=1 WORKDIR /app COPY poetry.lock pyproject.toml ./ RUN pip install poetry RUN poetry config virtualenvs.create false \ && poetry instaldocker-compose.yml
ports
でローカルport8888とコンテナport8888を接続し、command
でjupyterを起動します。docker-compose.ymlversion: '3' services: eda: build: ./ user: root volumes: - ./:/app working_dir: /app ports: - "8888:8888" command: jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.token='' --port=8888使い方
ソース一式をローカルにcloneします。
git clone https://github.com/yolo-kiyoshi/poetry_docker.gitビルドとコンテナ起動
以下のコマンドによりDockerイメージのビルドとコンテナを起動できます。
docker-compose up -d※
Dockerfile
を変更しリビルドしたい場合、docker-compose up -d --build
を実行しますjupyterへのアクセス
コンテナ実行後、以下にアクセスすることでJupyter labにアクセスできます。
http://localhost:8888/labテスト実行
以下のコマンドで
tests/
配下のテストコードを実行できます。pytest※
pytest
の詳しい使い方は以下がわかりやすいです。
pytest ヘビー?ユーザーへの第一歩パッケージの追加
以下のコマンドでPythonパッケージを追加できます。
poetry add <追加したいパッケージ>感想
poetry
使いやすいと思います。
パッケージ作成が容易になったのもいいのですが、何よりpipenv
よりも.lock
ファイルを作成にかかる時間が短いなと感じます。(定量的にはみていません、感覚的にです)
今年はバンバンpoetry
を使って開発していこうと思いました。参考
- 投稿日:2020-01-13T15:10:12+09:00
Golang on DockerでEchoを動かす
TL;DR
Docker上でGolangのWebフレームワークであるEchoを動かしてみました。
DockerでGolang環境の構築
FROM golang:1.13.6-alpine WORKDIR /go/src COPY ./src /go/src RUN apk update && apk add git #RUN go get -u github.com/labstack/echo/...
dockerfile
内のgo get -u github.com/labstack/echo/...
が実行出来なかったのは何故だろう。。。docker-compose.ymlversion: '3' services: app: build: . ports: - 8080:8080 volumes: - ./src:/go/src tty: true$ mkdir srcsrc/server.gopackage main import ( "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, Echo World!!") }) e.Logger.Fatal(e.Start(":8080")) }$ docker-compose build $ docker-compose up -dEchoのインストール
https://echo.labstack.com/
https://echo.labstack.com/guide$ docker-compose exec app go get -u github.com/labstack/echo/... $ docker-compose exec app go run server.go ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.1.13 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:8080出来ました
本当はdocker-compose up -d
だけでEchoの実行までいきたいんですがね。。。
- 投稿日:2020-01-13T14:36:40+09:00
Apache Guacamoleをdocker-composeで構築する
感想
- ブラウザだけあればリモートアクセスが出来そう(利用者がうれしい)
- 操作ログや画面操作を動画で残すことが出来そう(管理者がうれしい)
- docker版だとTomcatとか諸々のライブラリのインストールを考えなくて良くて構築が楽っぽい(自分がうれしい)
Apache Guacamoleとは
OSSのリモートアクセスゲートウェイソフトで、クライアントはHTML5対応のWEBブラウザだけで利用できる。使える接続プロトコルはVNC,RDP,SSH,TELNETがあり、Apache License、Version 2.0が適用されたOSS。またTOTPによるMFA(個人的に最近気に入っている)もできる。詳細はこちら公式にて。
構成イメージ
※画像は公式より。きっかけ
仕事で使っているリモートアクセス環境が、Windows Server 2008サポート終了のあおりを受けて更新しないといけなくなった。この際なのでセキュリティレベルを確保しつつ、コストを抑えた構成を考えようと思いついた。Windows OSからLinuxへ、有償シンクラソフトからOSSのApache Guacamoleへ。あわせてWEBブラウザだけでクライアント側に専用ソフトが不要なので、今まで温めていたアイデアが実現出来そうと言う淡い期待があった。今回はその際に実施した検証メモ。
前提
・環境
検証用環境・OS
Ubuntu 18.04・その他
Docker and Docker-Compose はインストール済み。適当にdocker-compose.ymlを作る
公式ドキュメントの手順を読むと、3種類のコンテナイメージを起動する必要がありそうなので、docker-composeで構築することにした。※以下のコンテナの役割は公式をGoogle翻訳。
guacamole/guacd
リリースされたguacamole-serverソースから構築されたguacdデーモンを提供し、VNC、RDP、SSH、およびtelnetをサポートします。
guacamole/guacamole
Tomcat 8内で実行されているGuacamole WebアプリケーションにWebSocketのサポートを提供します。guaker、MySQL、PostgreSQL、LDAPなどに接続するために必要な構成は、Dockerリンクまたは環境変数に基づいてイメージが開始されるときに自動的に生成されます。
mysql または postgresql
Guacamoleが接続構成データの認証と保存に使用するデータベースを提供します。
docker-compose.ymlの中身
本番環境はもうちょっと考える必要がある。
nginxはとりあえず的な感じで。docker-compose.ymlversion: "3" services: postgres: image: postgres:latest restart: unless-stopped environment: PGDATA: /var/lib/postgresql/data/guacamole POSTGRES_DB: guacamole_db POSTGRES_PASSWORD: guacamole1234567890 POSTGRES_USER: guacamole_user volumes: - ./pginit:/docker-entrypoint-initdb.d - ./pgdata:/var/lib/postgresql/data guacd: image: guacamole/guacd:latest restart: unless-stopped guacamole: image: guacamole/guacamole:latest restart: unless-stopped ports: - "8080:8080" environment: GUACD_HOSTNAME: guacd POSTGRES_DATABASE: guacamole_db POSTGRES_HOSTNAME: postgres POSTGRES_PASSWORD: guacamole1234567890 POSTGRES_USER: guacamole_user depends_on: - postgres - guacd nginx: image: nginx:latest restart: unless-stopped ports: - "80:80" volumes: - ./nginx/conf.d:/etc/nginx/conf.ddefault.confの中身
ディレクトリを作って、そこに置く。
# mkdir -p ./nginx/conf.ddefault.confserver { listen 80; server_name localhost; location /guacamole/ { proxy_pass http://guacamole:8080/guacamole/; root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }初期化用のSQL作成
PostgreSQLは公式Dockerイメージの初期化機能を使います。初期化スクリプト(.sql or .shとか?)が/docker-entrypoint-initdb.dディレクトリ内で見つかった場合に1度だけ実行されるらしいので、そこに作成しておく。
# mkdir ./pginit # chmod -R +x ./pginit # docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./pginit/initdb.sql起動
# docker-compose up -d # docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------- gucamole_guacamole_1 /opt/guacamole/bin/start.sh Up 0.0.0.0:8080->8080/tcp gucamole_guacd_1 /bin/sh -c /usr/local/guac ... Up 4822/tcp gucamole_nginx_1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp gucamole_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp # docker-compose logs -f動作確認
http://<IPアドレス or ホスト名>/guacamole/
ユーザー名:guacadmin
パスワード:guacadminでログイン。
TOTPの設定
公式(ver1.0.0の場合)からTOTP用 jarファイルをダウンロードししてくる。その後、${GUACAMOLE_HOME}/extensions ディレクトリに置いて起動させる必要がある。なのでdocker-compose.ymlファイルをちょっと編集し3行追加。
docker-compose.yml(抜粋)guacamole: image: guacamole/guacamole:latest restart: unless-stopped ports: - "8080:8080" environment: GUACD_HOSTNAME: guacd POSTGRES_DATABASE: guacamole_db POSTGRES_HOSTNAME: postgres POSTGRES_PASSWORD: guacamole1234567890 POSTGRES_USER: guacamole_user GUACAMOLE_HOME: /etc/guacamole ⬅️① volumes: ⬅️② - ./guacamole:/etc/guacamole ⬅️③ depends_on: - postgres - guacdディレクトリを作る。
# mkdir -p ./gucamole/extensionsダウンロード&解凍したTOTP用jarファイルをコピー。
# cp ~/Downloads/guacamole-auth-totp-1.0.0/guacamole-auth-totp-1.0.0.jar ./guacamole/extensions再起動
docker-compose down docker-compose up初回 or 次回の認証後、登録画面が表示されます。あとは「Google Authenticator」や「IIJ SmartKey」で登録しておくと、次回以降は6桁のコード入力を求められます。
ちょっとハマったとこ
ユーザにパスワード変更権限を付与しておかないとこのTOTPが有効にならないようです。
- 投稿日:2020-01-13T12:55:10+09:00
Dockerの手ほどき
Dockerとは
簡単に軽量な仮想マシンを作ることができます。
コンテナ型のアプリケーション実行環境です。
※コンテナについてはこちらの記事がわかりやすいです!そもそも仮想マシンとは?
仮想マシンとは1台のコンピュータで複数のコンピュータを動かす技術です。
手元にあるMacの中にWindows10やCentOSなど沢山のOS入るイメージです。なぜ仮想マシンを使うの?
実際に動いているWebサービスは「本番環境」で稼働しています。
開発時は実際に動いているものを触る訳にはいかないので、開発用の環境を準備します。
※開発環境は本番環境と一緒の設定にする必要があります。理由1: バージョンが違うとエラーが吐かれる等障害の元になる。
理由2:「本番環境しか再現しない」という現象が起き得る。VirtualBox+Vagrantを使って仮想環境を作ることは可能ですが、
・メモリを沢山消費してしまう
・個別にソフトなどインストールをするなど手間がかかります。
(例:ソースコード・DBサーバーなど・・・)→容易にしたものがDockerです。
Dockerの知識
Docker は Dockerfile から命令を読み込み、自動的にイメージを構築できます。
Dockerの基本用語 意味・役割 Image コンテナを構成するアプリケーション、設定に関する情報をまとめたもの。 コンテナ Imageをもとに具現化したアプリケーションの実行された環境。 DockerFile イメージの構成を定義したファイル。命令(インストラクション)を書いていきます。 Docker Fileの書き方
DockerFileはbuildでファイルに書かれた命令を順次実行してイメージを自動構築します。
あくまで「イメージを構築するための手順」です。1コンテナ1DockerFileです。
FROM : ベースとなるDockerImageを指定。(一番最初に書くこと!) RUN : Build時にコンテナ内で実行するコマンドを定義 WORKING : ディテクトの指定 COPY : コンテナの中へファイルを送るコマンド。 ADD : コンテナの中へファイルを送るコマンド。COPYにはないファイル展開機能がある。 ENV : 作ったコンテナ内のみで使える環境変数の設定 ※DockerBuildした時、まずFROMで指定したImageを Downloadします。Dockerコンテナのライフサイクル
実行中
・ DockerFileをもとに定義されている命令を実行するとなる。
・ 基本的には異常終了するまで実行し続ける。停止
・ユーザーが明示的にSTOPするとなる。
・実行されているアプリケーションが終了した時に自動停止する。廃棄
・ 完全に不要になったら廃棄をしましょう(残ったままだとディスクを専有するため。)複数のコンテナを立ち上げる
仕事で開発をする際、一般的には複数のコンテナを立上げる必要があります。
コンテナを立上らせる「docker build」を何度も実行するのは効率が良くありません。
そんな時はDocker composeという機能を利用します。docker psでコンテナの情報を確認する。
項目名 コンテナのステータス内容 CONTAINER ID コンテナに付与される一意のID IMAGE コンテナ作成に使われたDockerImage COMMAND コンテナで実行されるアプリケーションのプロセス CREATED コンテナが作成されてから経過した時間 STATUS コンテナの実行状況。up(実行中)、Exited(終了) PORTS HostPortとコンテナPortの組みづけ NAMES コンテナ付けられた名前 Docker composeとは
複数のコンテナをコードで管理します。
DB用、アプリケーション用など複数のコンテナからなるサービスを簡単に自動的に構築できます。Docker composeを使う3-Step
- DockerFileを準備
- docker-compose.ymlを作り、コンテナの起動定義を書きます
- docker-compose up を実行(docker-compose.ymlに書かれたコンテナが起動します)
docker-compose.ymlの書き方
docker-compose.ymlを書く時、よく使うものなどをまとめてみました。
SERVICE : hoge_name : サービス名 image : 使用するDockerImage links : コンテナを他サービスへ繋げる container_name : コンテナ名を指定する(ない場合が自動的に命名される) enviroment : 環境変数を追加 volumes : マウントするディレクトリ ports : exportするディレクトリ(ホストポート=コンテナポート)docker-compose コマンド
build : docker-compose.ymlで書いたserviceで指定したビルドする restart : サービスの再起動をする run : サービスを起動 up : サービスのimageをビルド・起動(build+run) exec : サービスに対してコマンド実行 logs : サービスのログを出力※コンテナIDを指定してdocker-conpose upすると、特定コンテナだけ起動することができます。
イメージ構築 コンテナ構築 コンテナ起動 build ○ × × up ○ ○ ○ start × × ○ run ○ ○ ○
- 投稿日:2020-01-13T10:51:21+09:00
DockerのコマンドをVagrantのコマンドと並べて理解する
はじめに
今までVagrantを使っていてこれからDockerを学びたい人(自分を含む)向けにVagrantとDockerのコマンドを機能別に対応関係をまとめました。
厳密に言うと異なりますが、概要の理解に繋がれば幸いです。
まだDockerの勉強中の身なので、アドバイス等あればコメントでお待ちしています。Vagrantとは
仮想環境を管理するためのソフトウェア
参考
Dockerとは
コンテナ型の仮想環境を管理するソフトウェア
参考
- 公式サイト
- 公式リファレンス
- Docker入門(第一回)~Dockerとは何か、何が良いのか~
- https://qiita.com/shiro01/items/04ca672a93384b463701
概念の対応関係
Vagrant Docker ホストOS Linux,Windows,mac OS etc. Linux 扱う仮想化技術 ホストOS型 コンテナ型 仮想環境ベース パッケージ イメージ 仮想環境 Virtual Machine(VM) コンテナ 設定ファイル Vagrantfile dockerfile コマンドの対応関係
ここではオプションは割愛します。
Docker v1.13以降でコマンド体系が変わったようなので可能な限り新旧どちらのコマンドも記載します。旧コマンドは「何を」操作しているか見えなかったのが、新コマンドは「何を」の部分が必要になったためVagrantの対応関係も比較的わかりやすいです。仮想環境ベースの操作
操作 Vagrant Docker 追加 vagrant box add (新)docker image pull
(旧)docker pull一覧 vagrant box list (新)docker image ls
(旧)docker images作成 vagrant package (新)docker image build
(旧)docker build削除 vagrant box remove (新)docker image rm
(旧)docker rmi仮想環境の操作
操作 Vagrant Docker 初期化 vagrant init - 起動 vagrant up (新)docker container start
(旧)docker start一覧 vagrant global-status (新)docker container ls
(旧)docker ps終了 vagrant halt (新)docker container stop
(旧)docker stop削除 vagrant destroy (新)docker container rm
(旧)docker rmssh接続 vagrant ssh (新)docker container exec -it {コンテナID} bash
(旧)docker exec -it {コンテナID} bash参考
まとめ
- Vagrantのpackage = Dockerのimage
- VagrantのVM = Dockerのコンテナ
- Dockerの新コマンドと一緒に覚えると分かりやすい
- 投稿日:2020-01-13T10:47:10+09:00
Docker を使う(docker swarm でアプリケーションをデプロイする)
前回の記事 の続きです。
ようやく完成しました・・・
今回は、コンテナオーケストレータの一つである Docker Swarm を使い、アプリケーションをデプロイしてみます。
アプリケーション開発からやっていてはいくら時間があっても足りないので、事前に用意しました。
GitHub にて公開していますので、 Clone もしくはダウンロードをお願いします。
https://github.com/landwarrior/flask-restful
こちらをベースに話を進めていきます。アプリケーション仕様
今回用意したアプリケーションを箇条書きで説明します。
- サーバーは 3 台必要
- Docker 用が 2 台、 MySQL 用が 1 台
- Vagrant を使用して VirtualBox 上に 3 台分を構築します
- アプリケーションは Python で実装
- Flask-RESTful を使用し、 REST API を構築
- VM の構築に Chef Infra Client を使用
- 誰でも同じように構築できるようにするため、 Chef を使用
- VM の構築の簡略化のため、シェルスクリプトをそれぞれ用意
- シェルスクリプトがほぼ全部やってくれます
- 冪等性が保たれているかは微妙です
- かなりやっつけなので書き方がおかしいところもあるかもしれないけど、気にしない
ということで、説明していきます。
VM の準備
GitHub からまるっと取得いただければ、ほぼ README.md に書いてありますが、ちゃんと説明します。
VM を構築するための前提条件は、以下の通りです。
- Windows 10 Pro バージョン 1909
- VirtualBox 6.0.14
- Vagrant 2.2.6
VirtualBox の最新バージョンは 6.1 ですが、執筆時点で Vagrant の 2.2.6 は VirtualBox 6.1 に対応していないようなので、6.0 系を使う必要があります。
この時点で厄介です。。。?フォルダの準備
VM は 3 台必要なので、フォルダを 3 つ用意しましょう。
自分の場合は、以下のようにしました。
- D:\VirtualMachines\Vagrant\flask-restful-primary
- D:\VirtualMachines\Vagrant\flask-restful_secondary
- D:\VirtualMachines\Vagrant\mysql-server
フォルダ名は自身が分かればなんでもいいと思います。
Vagrantfile の準備
コマンドプロンプトを立ち上げ、それぞれのフォルダで以下のコマンドを実行します。
vagrant init bento/centos-7.7それぞれのフォルダ内に作成された Vagrantfile を修正します。
アプリケーションの都合上、 IP アドレスは指定があります。
すでに使用済みの IP アドレスの場合、任意の IP アドレスを指定し、適宜読み替えてください。
アプリケーション上でも IP アドレスを記載している箇所があるので、その点の修正も忘れずに。primary の場合
config.vm.network
の設定
- コメントアウトを外し、
config.vm.network "private_network", ip: "192.168.33.10"
とします- ホスト名を付ける(必須ではない)
- config.vm.network の下に、
config.vm.hostname = "flask-primary"
を記載しますconfig.vm.synced_folder
の設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
secondary の場合
config.vm.network
の設定
- コメントアウトを外し、
config.vm.network "private_network", ip: "192.168.33.11"
とします- ホスト名を付ける(必須ではない)
- config.vm.network の下に、
config.vm.hostname = "flask-secondary"
を記載しますconfig.vm.synced_folder
の設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
mysql-server の場合
config.vm.network
の設定
- コメントアウトを外し、
config.vm.network "private_network", ip: "192.168.33.20"
とします- ホスト名を付ける(必須ではない)
- config.vm.network の下に、
config.vm.hostname = "mysql-server"
を記載しますconfig.vm.synced_folder
の設定
- GitHub から取得いただいたソースを VM 上にマウントするため、
config.vm.synced_folder "D:/program_src/flask-restful", "/vagrant_data"
のように指定します(ソース配置場所は適宜読み替えてください)- バックスラッシュではなくスラッシュに変更する必要あり
vagrant up していく
Vagrantfile の修正が終わったので、順次 vagrant up していきましょう。
コマンドプロンプトでそれぞれのフォルダに cd してvagrant up
を実行するだけですね。環境構築
ここを簡略化するために、シェルスクリプトを用意しました。
説明が大変なので・・・
それぞれの環境ごとに異なるシェルスクリプトを実行します。primary の場合
VM にログインしたら、以下を実行しましょう。
sudo /vagrant_data/provisioning_primary.sh諸々インストールし、 chef で docker をインストールし docker build までやってくれます。
最新バージョンが入るので、将来的に動かなくなることもありそう?Docker Swarm の構築までやってくれちゃいますが、コマンドとしては以下を実行しているだけです。
docker swarm init --advertise-addr 192.168.33.10これは、 Docker Swarm のリーダーとなるサーバーでのみ実行します。
オプションの意味は、よく分かっていません?
リーダーとなるサーバーの IP アドレスを指定すればちゃんと動くので、あまり気にしていませんね。。。docker swarm init の後に、以下のコマンドも実行しています。
docker stack deploy --with-registry-auth -c /var/app/docker/docker-local.yml testこれは、 docker swarm 上でサービスを起動するときのコマンドです。
どのようにコンテナを起動するかを docker-local.yml で定義し、指定しています。
また、最後のtest
は、 docker swarm で立ち上げた際のサービス名です。
これがコンテナ名の先頭につきます。だいぶ端折り気味ですが、セカンダリの説明に移ります。。。
secondary の場合
以下のコマンドを実行します。
sudo /vagrant_data/provisioning_secondary.shすでに docker swarm のリーダーはいるので、こちらは join する側になります。
join するためには、リーダーでトークンを発行する必要があります。
そのため、 primary の方で以下のコマンドを実行しましょう。docker swarm join-token workerそうすると、コマンドが表示されるかと思います。
そのコマンドをそのままコピペして実行しましょう。
実行すると、自動でコンテナが立ち上がります。
docker ps
コマンドで確認できるかと思います。ただし、 docker イメージはローカルにある必要があるため、シェルスクリプト内でこちらも docker build をしています。
Docker Hub のように外部にイメージがあり、そこから pull できる形であれば、ローカルで docker build しておく必要はないはず・・・?docker swarm の状態を確認
primary の方で以下のコマンドを実行してみましょう。
docker node lsすると、 secondary のサーバーが docker swarm の node として追加されているのが確認できます。
また、以下のコマンドでサービスの状態を確認可能です。docker service lsコンテナが片肺になっているかどうかなどを確認する時に使ったりします。
docker stack deploy
したのにコンテナがうまく起動できないな、という時は、以下のコマンドで確認することができます。docker stack ps test --no-truncこれは、
test
という名称で立ち上げた docker swarm のサービスのコンテナの状況を確認するコマンドです。
起動に失敗しているログが見えたりしますが、 docker swarm は起動できるまでコンテナの再起動を繰り返すので、多少のエラーは気にしなくても大丈夫です。docker swarm で構築されたネットワークを確認するためには、まずネットワークの一覧を取得してみましょう。
docker network lsデフォルトでは、
サービス名_default
という名称でネットワークが作成されます。
今回の例では、test_default
となります。
では、このネットワークの詳細を確認しています。docker network inspect test_defaultネットワークに所属するコンテナが見れます。
なお、 docker swarm で構築されたネットワークはデフォルトではアタッチ不可のため、 docker stack deploy した時に起動されたコンテナ以外はネットワークに入れません。
後でネットワークにコンテナを追加したい場合は、 docker-local.yml でしれっと指定しているのですが、attachable
の設定をする必要があります。
ただし、アタッチ可能にした場合、 docker run でネットワークを指定し、アタッチした際にネットワークの再構築が行われるらしく、瞬断が発生することがあるようです。
そのため、アタッチ可能にすることは非推奨かもしれません。今度は、コンテナの詳細情報を見てみましょう。
以下のようなコマンドで確認可能です。docker service inspect test_flask簡略表示したい場合は、
--pretty
オプションを付けます。コンテナのログを見る場合は、以下のコマンドで確認します。
docker service logs -f test_flaskdocker-local.yml で log-driver の指定をすれば、上記コマンドでなくてもログを指定した方法で転送可能ですが、ここでは取り扱いません。
そこまでの時間はありませんでした。。。
よく使われるのは、 fluentd とかですかね。だいぶ書きなぐってしまいましたが、まだ MySQL の VM を構築してないので次に進みます。
mysql-server の場合
以下のコマンドを実行します。
sudo /vagrant_data/provisioning_db.shMySQL をインストールし、
mydb
という DB を構築し、あとはソース上にあるinit.sql
を実行するだけです。
ユーザー追加とテーブル追加をするだけですね。これで構築完了です。
動作確認
REST API なので、動作確認をしてみましょう。
mysql-server で、 curl コマンドで確認可能です。
例えば、以下のコマンドをそれぞれ実行してみてください。curl http://192.168.33.10 curl http://192.168.33.10/v1/groups curl http://192.168.33.10/v1/usersJSON 形式でデータが返ってきたと思います。
チープな機能しか実装していないため、ユーザーとグループの CRUD しかできません。
さらに、ユーザーとグループは n:1 で紐づくようなテーブル構成になっていますが、 REST API の戻り値では特にそこを意識していません。
そこまでは手が回りませんでした・・・?REST API は、以下のように操作します。
# 全グループを取得 curl http://192.168.33.10/v1/groups # 指定したグループIDのデータを取得 curl http://192.168.33.10/v1/groups/1 # グループを追加 curl http://192.168.33.10/v1/groups -XPUT -H"content-type:application/json" -d'{"group_name": "group1"}' # グループを更新 curl http://192.168.33.10/v1/groups/1 -XPOST -H"content-type:application/json" -d'{"group_name": "test_group1"}' # グループを削除 curl http://192.168.33.10/v1/groups/1 -XDELETE # 全ユーザーを取得 curl http://192.168.33.10/v1/users # 指定したユーザーIDのデータを取得 curl http://192.168.33.10/v1/users/1 # ユーザーを追加 curl http://192.168.33.10/v1/users -XPUT -H"content-type:application/json" -d'{"user_name": "user1", "group_id": 1}' # ユーザーを更新 curl http://192.168.33.10/v1/users/1 -XPOST -H"content-type:application/json" -d'{"user_name": "test_user1"}' # ユーザーを削除 curl http://192.168.33.10/v1/users/1 -XDELETEそれぞれ動作確認してみてください。
終わりに
ちゃんと動いたら、めでたしめでたし。
ちなみに、 primary をvagrant halt
で落としても、192.168.33.11
の方で動作はできます。
primary を立ち上げなおすと、元通りになるみたいです。
なお、 secondary を worker ではなく manager として join すると、 primary を停止して立ち上げなおすとリーダーと manager が入れ替わりました。
この挙動の違いがどう影響するのかが分かっていません。。。余談
拙くても、ようやく Docker Swarm まで記事にできました。
実際にこれを試してみようと思われた方、ありがとうございます。
記事にした甲斐がありました。ただ、職場で試される方は要注意です。
プロキシ環境下で試される方は、 Docker のプロキシ設定が必要です。
以下のディレクトリを作成し、コンフィグファイルを配置する必要があります。/etc/systemd/system/docker.service.dproxy.conf[Service] Environment="HTTP_PROXY={proxy url}"ファイル名は適当、 url は適宜設定してください。
また、 docker build 時にも proxy 設定が必要です。docker build --build-arg HTTP_PROXY={proxy url} --build-arg HTTPS_PROXY={proxy url} -t hoge .シェルスクリプトを書き換えて実行しましょう。
また、場合によっては SELinux も無効にしないといけないかも。
環境に合わせて構築しましょう。ということで、ひとまず Docker の記事はこれにて終了です!
お疲れさまでした?
- 投稿日:2020-01-13T03:15:51+09:00
docker-composeでRundeckを起動してみる
豆知識Rundeckの404画面はスペース猫
docker-compose.ymlversion: '3' services: rundeck: image: rundeck/rundeck:SNAPSHOT links: - mysql environment: RUNDECK_DATABASE_DRIVER: com.mysql.jdbc.Driver RUNDECK_DATABASE_USERNAME: rundeck RUNDECK_DATABASE_PASSWORD: rundeck RUNDECK_DATABASE_URL: jdbc:mysql://mysql/rundeck?autoReconnect=true&useSSL=false RUNDECK_GRAILS_URL: http://example.com:4440 volumes: - ${RUNDECK_LICENSE_FILE:-/dev/null}:/home/rundeck/etc/rundeckpro-license.key ports: - 4440:4440 mysql: image: mysql:5.7 expose: - 3306 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=rundeck - MYSQL_USER=rundeck - MYSQL_PASSWORD=rundeck volumes: - ./dbdata:/var/lib/mysqldocker-compose up -dログイン画面では
Usernameはadmin
、Passwordはadmin
でログインできるrundeck/rundeck - Docker Hub
Running Docker Images | Rundeck Docs
docker-zoo/mysql at master · rundeck/docker-zoo
docker-zoo/docker-compose.yml at master · rundeck/docker-zoorundeckをdocker-composeで使う - Qiita
Rundeckによるジョブスケジューリング - Qiita
ジョブスケジューラ「Rundeck」を試してみる | Developers.IO
必殺ジョブスケジューラ!!Rundeck - Qiita
- 投稿日:2020-01-13T02:00:30+09:00
手っ取り早くGUI操作のできるコンテナをつくる
Apache Guacamoleというものを使うとGUI環境の無い踏み台からも楽にGUI操作を行えるようになって便利そうだったため、これを使ってなるべくコストをかけずに簡単にGUI操作を行えるようにする方法を考えてみました。
↑ブラウザの中でブラウザを開いている様子
(正確にはブラウザの中でXが動いてるLinuxのデスクトップを開いて、その中でブラウザを起動している)TL;DR
- クローズド環境で外部端末のブラウザから内部のネットワーク内のブラウザを開けるようにするよ
- DockerでGuacamoleとSeleniumのデバッグ用のコンテナたてるよ
- ブラウザからWindowsにRDPとかもできるよ
GitHub
https://github.com/sensq/bastion-guacamole概要
一般的な環境だと需要はほとんど無いと思いますが、本番環境などのクローズドな環境では外部からは踏み台サーバを経由しての接続しか許されておらず、内部のWebアプリケーションやWindowsなどをGUI操作したいときにどうするかで困ったりします。
踏み台サーバにGUI環境が用意されていれば楽ですが、サーバをGUI化すると設計や構築や構成管理のコストが増えたり、いつの間にか便利ソフトで溢れて運用がブラックボックス化したりして嫌なので、ただ踏み台経由でhttpやRDPで接続したいためだけにGUI化をするのはなるべくやりたくないです。別にシンプルにポートフォワードで繋ぐなどでも良いのですが、今のコンテナの時代、もっと楽で便利で汎用的なやり方があるのではないかと思い、上記のような環境で手っ取り早くGUI操作を行えるようにする方法を考えます。
Guacamoleの説明
Guacamoleとは、ネットワーク内のサーバへWeb上からRDP/Telnet/SSH/VNCで接続できるようにする正に踏み台のためにあるかのようなOSSのツール。「グアカモーレ」とか「ワカモレ」と読むらしい。
アーキテクチャなどの詳細はこちらのページがわかりやすい。
https://dev.classmethod.jp/cloud/aws/setup-apache-guacamole/要するに踏み台サーバへGuacamoleを入れれば、外部のPCのブラウザから踏み台のGuacamoleにアクセスしてブラウザからWindowsに接続したり、ブラウザの中でクローズド環境内のブラウザを開いて外部からは見られないページを見たりすることができるようになる。
作るもの
Guacamole自体は結構古く、公式でイメージも提供されており、ググってみるとDockerが流行りだして間もない2016年や2017年に既に同様のことをやっている記事が出てくる。
ただ、ブラウザで操作するUIがどんどん一般的になってきている昨今、踏み台からはVNCやRDPで接続するだけでなくブラウザを動かしてhttp(s)で接続したい需要も高まってきていると思われるため、Guacamoleと一緒にブラウザが動くだけのVNCで接続できるコンテナがあれば非常に便利そうである。しかし残念ながらGoogleやMozillaはそんなコンテナイメージを提供しておらず、自分でDockerfileを用意してビルドするのは嫌なため、本来の用途とは異なるがSeleniumのデバッグ用のイメージを利用することにする。
https://hub.docker.com/r/selenium/standalone-chrome-debug
https://hub.docker.com/r/selenium/standalone-firefox-debugこのイメージはとても都合がいいことにコンテナを起動すると中でVNC Serverも起動するため、Guacamoleコンテナと一緒に起動させるだけで今回の目的を果たすことができる。
よって、必要なものは既に公式イメージで提供されているものだけで十分なため、作るものは本質的にはdocker-compose.yamlのみでよい。また、当然だが別に1つの踏み台上で一緒に動かす必要はなく、同一ネットワーク内であればstandaloneのコンテナを別のサーバで動かすことも可能である。そのため、踏み台から直接アクセスできてしまうことを嫌がる場合や、踏み台がネストされているような場合にも対応することができる。
作ったもの
詳細はGitHubを参照
起動手順もこちらを参照
https://github.com/sensq/bastion-guacamole上述した通り、公式のコンテナを組み合わせて起動するだけのため自分で作る必要のあるものは本質的にはdocker-compose.yamlのみ。ただし、手間を減らすために他にもいくつかファイルを置いている。
全体的になるべく作り込まないように作成しているため、必要があれば適宜カスタマイズしてください。
docker-compose.yaml
- 最初DBはMySQLにしていたが、Postgres:alpineのイメージの方が300MBくらい軽かったのでPostgresを使うようにした
- postgresコンテナにマウントしている
initdb.sql
はinit.sh
を実行すると生成されるファイルのため、コンテナ起動前に実行しておくこと
- ※ファイル存在しない状態でコンテナ起動してしまうと
initdb.sql
という名前のディレクトリが作成されてしまうので注意user-mapping.xml
- Guacamoleの接続設定を記載したデフォルトの設定ファイル
- どの環境でも毎回同じ設定で作るものなため、特に変更する必要はない
- これで作成した設定はSettingsのConnectionsに出てこない(DBに保存されていない)
- ユーザ名とパスワードはデフォルトの
guacadmin/guacadmin
にしているため、変えたかったら変えてもいい(書き換えてコンテナ起動すれば勝手にユーザが作られる).env
docker-compose down
でコンテナ落とすときに数分かかるため、エラーにならないようにタイムアウトの値を増やしているだけinit.sh
- DB初期化用のSQLを生成するコマンドを実行するだけ
- 普通にguacamoleの構築手順の中に書かれているコマンド
- ※万が一
initdb.sql
というディレクトリを作ってしまった場合はスクリプト実行前にディレクトリ削除することstart_browser.sh
- standaloneのコンテナ内でブラウザを起動するコマンドを実行するだけ
- Chromeは普通に初めて起動したときのWelcomeページがやたらCPU使う重いページだったので適当なURLを指定して起動している。クローズドな環境だと404になりそうだが特に問題ないので気にしない。
なお、初回起動のみデータベースの初期化処理が行われ、完了するまではGuacamoleのURLにアクセスしてもログイン画面が出てきません。
数分で完了するはずです。イメージのサイズ
参考までに2020/1/13時点での各イメージのサイズを載せておきます。合計で約2.5GBです。
Repository Tag Size --------------------------------------------------- selenium/standalone-chrome-debug latest 815 MB selenium/standalone-firefox-debug latest 751 MB guacamole/guacamole latest 468 MB guacamole/guacd latest 377 MB postgres alpine 139 MBMySQLはAlpine版が無く、latestは422MBでした。
使用感
普段遣い用として使うのはストレスたまりそうだが、踏み台の中でのみ使うものとすれば許容範囲な気はする。
まだ検証で試用してみただけなので使い続けていくと色々不満点や改善点が出てくるかもしれない。参考
Guacamoleを動かすためのdocker-compose.yamlの書き方は以下2つの記事を参考にしました。
本記事よりも詳細にGuacamoleの設定方法などが書かれています。
https://kakakakakku.hatenablog.com/entry/2019/01/09/001605
https://blog.1q77.com/2019/01/guacamole-functions/
- 投稿日:2020-01-13T01:31:15+09:00
dockerfileについて調べてapache起動するまでのメモ
はじめに
前回はDockerのコマンド操作を学ぶために主な操作の記事を書かせて頂きました。
他にもいろいろ調べていたらdockerfileなるのもがありそれを使用してイメージを作成するとのことでした。
見た感じ難しそうで抵抗があったのですが、そういうものこそ試していかないといけないと思いやってみましたので備忘録件まとめ用として記事にしてみたいと思います。dockerfielってなんぞ?
コンテナを作成する場合、docker hubから欲しいイメージを取得してコンテナを作成したり、最小限のイメージから自分で新しくミドルウェアを追加したものをイメージ化して展開したりするとたくさんの情報が出ていましたが、自分でイメージを一から作成する場合などに使用するファイルです。
dockerfileはベースとするイメージに対して行う操作を記載する。dockerfileを使用する利点
- OSや各種ミドルウェアなどの設定をコードとして管理できる
- 上記により細かく設定した環境を漏れなく簡単に再作成したり同一環境を用意したい人に配布できる。
- jenkinsなどのCIツールを使用することでイメージ作成やコンテナのデプロイを自動化することもできる(らしい)
書き方について
調べてみたのですが結構難しそうでした(笑)
まずは簡単にコードについて
- FROM イメージ名 →ベースにするイメージを指定する
- LABEL →管理用の情報などを記載する バージョン情報やなにを作るためのdockerfileですよ的な
- RUN →コマンド実行を命令するリターンキーの役割(こいつがコマンドを実行していく感じ)
- CMD →コンテナ起動時にファイルやコマンドを実行する命令を記載する
- ENV →環境変数を設定する
おおまかにはこんな感じでした。ほかにもたくさんありましたが、いったんこれくらいにしておきました。(覚える自信ない)
実際に書いてみる
簡単にapache用のdockerfileを書いてみます。
dockerfileFROM centos:centos7 #ベースになるイメージ指定 RUN yum -y update #パッケージ更新 RUN yum -y upgrade #更新、不要なもの削除(してくれるらしい) RUN yum install -y httpd #apacheインストール RUN systemctl enable httpd #apache自動起動設定 EXPOSE 80 #ポート開放できたので上記ファイルを使用してイメージの作成をしてみます。
$ docker build -t apache .ずーーーーと実行されて終わったみたいなのでイメージができてるか確認してみます。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE apache latest 827d4f21614f 15 seconds ago 599MBありました!apacheという名前で作成したのでできています。
イメージからコンテナ作成して起動
起動
$ docker run --privileged -d -p 80:80 apache /sbin/init 8a86bb505eca2b7143ac70a1033e34205ffbbccf48a3ca89e96031194b00ffd9確認
しっかり起動しているのがわかりました!$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8a86bb505eca apache "/sbin/init" 9 seconds ago Up 8 seconds 0.0.0.0:80->80/tcp sweet_hodgkin実際にアクセスして確認!!
テストページが表示されたので問題なさそうです!!!
ちなみにcentos7の場合はコンテナの起動を上記のようにしないとうまくapacheがうまく起動しないようです。(めっちゃはまった)
ここら辺の起動の仕方とコンテナ内の動きについては調べなおして書かせていただければと思います。まとめ
- Dockerfileはベースのイメージに対して行う命令を記載するもの
- 構築過程のコマンドや起動時の命令もコードとして管理できる
- 実行するだけで何回でも同じ環境をミスなく作れる
- まだまだ勉強しないとdockerを扱って環境構築は厳しそうw