- 投稿日:2019-12-24T23:52:22+09:00
Kaggle・機械学習実験用Docker環境構築入門
はじめに
初めましての方は初めまして,最近Volareという学生団体に所属することになったtattakaです.
普段は関西の大学院の修士課程でロボットビジョンの研究をしていて,趣味ではKaggleの主に画像コンペに参加しています.
この記事では機械学習の研究やKaggleで使いまわせる環境の構築について書いていこうと思います.
(この記事はVolare Advent Calendar 2019の24日目です.)この記事で書くこと
- Kaggleや機械学習系の研究室でDockerを使う利点
- ディレクトリ構成について
- DockerHubを使ったイメージの管理
書かないこと
- NVIDIA Docker・Docker Composeの話
- (このあたりは筆者が
怠慢諸事情により最新バージョンを追えていないので......)1. Kaggleや機械学習系の研究室でDockerを使う利点
具体的な環境構築の前に,Dockerを使ってKaggleや機械学習の環境を構築することの利点について述べていきます.
環境構築するときの虚無の緩和
ホスト環境に影響を与えないのでインストール方法などの試行錯誤がしやすいです.
また,既存環境との衝突をあまり考えなくてもよくなります.環境汚染からの脱却
同じイメージからは同じコンテナが生成されるので環境を汚してもコンテナを作り直せば良いので直で環境構築をするよりは環境が汚れにくいです.環境の共有が楽
Dockerfileを共有すれば基本的に(GPUドライバ周りを合わせていれば)同じイメージを生成できます.また、後に説明するDockerHubを使えばイメージをビルドする時間の省略のほか,docker pull
コマンドだけで違うマシンでも同じ環境を構築できるようになります.2. ディレクトリ構成について
私はKaggleや研究では深層学習を主に用いるので以下のようなディレクトリ構成で運用しています.
ml_environment ├── projects │ ├── project_dir1 │ ├── project_dir2 │ └── project_dir... ├── docker │ ├── base │ │ └── Dockerfile │ ├── framework1 │ │ └── Dockerfile │ ├── framework2 │ │ └── Dockerfile │ ├── framework... │ │ └── Dockerfile │ ├── scripts │ │ └──initialize-docker-container.sh │ └── docker-compose.yml └── RUN-DOCKER-CONTAINER.shそれぞれのファイル・ディレクトリの説明をしていきます.
projects
Kaggleや研究で用いるソースファイルを入れる場所です.ここに集めておくことによってJupyter Notebook
などをprojects
ディレクトリで開いた時にプロジェクト間の行き来がしやすくなります.
docker
Dockerfile
やdocker-compose.yml
などを入れる場所です.
フレームワークごとにDockerfile
を分けたディレクトリを作っていて,イメージを再利用できるようにしています.
base
ここにはフレームワークによらず共通で使うライブラリを固めるためのイメージを作成するDockerfileを置いています.
筆者の場合こんな感じで使っています.framework...
ここにはフレームワーク別のDockerfile
を置いています. 例えばPyTorch
だとこのようなファイルになります.scripts
ここにはDocker起動時や内部で動作させるスクリプトを配置しています. 例えばinitialize-docker-container.shはdbus
の起動・exit
をしてもバックグラウンドで動き続けさせるためのスクリプトです.
docker-compose.yml
「はじめに」でも述べた通り,詳しくは書きませんが必須な設定とやっておくと便利な設定をいくつか述べます.
volumes: - ../:/root/ml_environment/
ホスト上のプロジェクトディレクトリをDocker上にマウントします.command: /root/ml_environment/docker/scripts/initialize-docker-container.sh
上で設定したinitialize-docker-container.sh
を適用する設定です.network_mode: host
ネットワークデバイスをホストと共有する設定です.一般的にはポートマッピングを使った方が良いのですが,リモートでこのDocker環境を使うときSSHのポートフォワードだけでいろいろできるので便利です.
RUN-DOCKER-CONTAINER.sh
上のdocker-compose.yml
を使ってコンテナを立ち上げるスクリプトです.こんな感じで書くといいと思います.3. GitHubとDockerHubを使ったイメージの管理
なぜDockerHubを使った方が良いか
まず,DockerHubを使ってイメージを管理するメリットについて簡単に説明します.
- イメージをビルドする時間の省略
ローカルでイメージをビルドするのはマシンスペックによっては時間がかかってしまいます.また,ビルド時に大容量を要求するライブラリもありダウンロードするだけの方が時間短縮になる場合が多いです.- 違うマシンで同じイメージを使って環境構築できる
お気づきの方がいるかもしれませんが,上で紹介したDockerfile
の中のライブラリはバージョン指定をしていないものがあります.これは調べるのが面倒くさいからパッケージによってはaptのkeyが無効化されていてダウンロードできなくなっていたりunstableなバージョンだと消えていたりすることが考えられるからです.DockerHubを使うことでビルドする回数を最小限にして動作を担保したイメージを別のマシンに配布することが可能になります.DockerHubとGitHubリポジトリの連携方法
0. 準備
GitHubのリポジトリを準備しましょう.
ここからは筆者のこのリポジトリ(tattaka/ml_environment)を使って説明していきます.
またDockerHubにリポジトリを作るためにはユーザ登録が必要なので登録しておきましょう.1. リポジトリ作成
次にこのページ(https://hub.docker.com/repositories)に飛んで
Create Repository+
ボタンを押しましょう.
ここでリポジトリの各種設定ができるので必須項目を順に埋めていきましょう.
- Name: リポジトリ名を入力しましょう.筆者は
ml_environment
はもう使っているのでml_environment2
という名前を使います.- Visibility: リポジトリを公開するかどうかの設定です.無料アカウントだと非公開リポジトリは1つしか作れないみたいです......
- Build Settings: 外部のWebサービスと連携するかどうかの設定です.GitHubとBitbucketが選べますが今はおいておいて後で設定しましょう.
2. GitHub連携の設定
次にGitHub連携の設定をしていきます.
上の画面のBuilds
タブをクリックして,この画面に遷移します.
ここでLink to GitHub
を選択します.初めてリポジトリを作る場合は下のようなユーザ設定画面でGitHub連携の初期設定をする必要があります.2回目以降はGitHubの連携の初期設定は不要です.
ここでGitHubの行のConnect
ボタンをクリックします.
GitHubに遷移するのでAuthorize docker
を選択します.
GitHubのパスワードが求められるので入力してConfirm password
をクリックします.
これでDockerHubアカウントとGitHubアカウントの連携ができました.3. Automation Buildの設定
後もう一息です!
次にリポジトリのBuilds
タグに戻り,もう一度Link to GitHub
をクリックします.
ビルドの設定画面になるのでSelect Organization
に自分のユーザ名を入れ、Select Repository
で使いたいリポジトリを選択します.
最後に上の画面下部のBUILD RULES
を追加していきます.
今回は上で説明したbase
ディレクトリのDockerイメージがgit push origin master
をすると自動でビルドされるように設定していきます.今は行いませんがBranch
ではなくTag
と紐付けして自動ビルドを行うこともできます.
今回git push origin master
した時にビルドが走ってほしいのでSource
はそのままで大丈夫です.
Docker Tag
はこの場合base
と名付けましょう.
Dockerfile location
はリポジトリの1番上から見てDockerfile
がどこにあるかを設定します.
今回はdocker/base/Dockerfile
とします.入力したら保存しましょう.
保存が完了するとBuilds
タブに戻り設定が完了していることが確認できます.
git push origin master
することでもビルドが始まりますが,手動でビルドしたい場合,下部に先ほど設定した自動ビルドの項目があるのでTrigger ▶︎
をクリックするとビルドが始まります.
これで設定は完了です.お疲れ様でした!今設定してビルドしたDockerイメージは
docker pull tattaka/ml_environment2:base
でダウンロードすることができます.ぜひ試して見てください.最後に
本記事では筆者の思う使いやすい環境構築のTipsについて説明してきました.
Dockerの本来の使い方や思想とはあっていない(であろう)使い方のため,「もっとここをこうした方が良いよ」「ここおかしいんじゃない?」という意見があると思います.そのような場合はコメント欄に意見を送ってもらえるととてもありがたいです.
最後に、ここまで読んでいただき本当にありがとうございました.
- 投稿日:2019-12-24T23:34:24+09:00
Apache Ambari を Docker でインストール
Hadoop クラスタの環境を個人で実サーバで持つのはコストがかかるため、多少低スペックでも構わないので、個人のローカルPC上で出来る範囲で構築したいところです。
今回は以下の記事を元に Dockerfile を書いて Docker コンテナ上で Ambari を起動するところまでやってみます。
Apache Ambari Installation
Dockerfile を書く
- centos7 を使ってみます。
- いくつか使いそうなコマンドをインストールします。
- centos7 で service コマンドを使えるようにするため、initscripts をインストールします。
- Ambari repository をダウンロードしておきます。
- Ambari Server をインストールします。
Dockerfile# 1 FROM centos:centos7 LABEL maintainer "blueskyarea" USER root # 2. Install commands RUN yum install -y curl tar rsync wget # 3. Install initscripts for service command RUN yum -y install initscripts && yum clean all # 4. Download Ambari repository RUN wget -nv http://public-repo-1.hortonworks.com/ambari/centos7/2.x/updates/2.6.2.0/ambari.repo -O /etc/yum.repos.d/ambari.repo # 5. Install Ambari server RUN yum install -y ambari-serverDocker イメージを作る
今回はイメージ名として、"ambari262" と指定しておきます。
$ docker build -t ambari262 . double free or corruption (out) SIGABRT: abort PC=0x7fe1348bde97 m=0 sigcode=18446744073709551610 signal arrived during cgo execution (中略) Complete! Removing intermediate container 7b98ea9a218a ---> 7003da2b64b7 Successfully built 7003da2b64b7 Successfully tagged ambari262:latestDocker イメージが作成されました。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ambari262 latest 7003da2b64b7 4 minutes ago 1.27GB
コンテナを起動する
Ambari server では、デフォルトで組み込みの postgres を使用するようです。
postgres を起動するためには、systemctl コマンドが必要になりそうですが、centos7では、/sbin/init を実行する必要があるようです。$ docker run -d --privileged ambari262 /sbin/init 84c36c37ff468532962121011002ecb324f3df8edf3f16ae0e7016ac1dcd3033そして、そのコンテナに対して、bash を起動します。
$ docker exec -it 84c36c37ff46 /bin/bashAmbari server の setup
マニュアルに沿ってセットアップしていきます。
注意しておきたいのは、"Enter advanced database configuration y/n? "の質問に対して、No(n) で回答するところです。
Yes(y) を選択して、使用するDBを手動で選択した場合、自分でDBを起動し、スキーマの作成など行う必要があります。
※私はそれで詰まってしまったため、コンテナの再作成からやり直ししました[root@84c36c37ff46 /]# ambari-server setup Using python /usr/bin/python Setup ambari-server Checking SELinux... WARNING: Could not run /usr/sbin/sestatus: OK Customize user account for ambari-server daemon [y/n] (n)? Adjusting ambari-server permissions and ownership... Checking firewall status... Checking JDK... [1] Oracle JDK 1.8 + Java Cryptography Extension (JCE) Policy Files 8 [2] Oracle JDK 1.7 + Java Cryptography Extension (JCE) Policy Files 7 [3] Custom JDK ============================================================================== Enter choice (1): To download the Oracle JDK and the Java Cryptography Extension (JCE) Policy Files you must accept the license terms found at http://www.oracle.com/technetwork/java/javase/terms/license/index.html and not accepting will cancel the Ambari Server setup and you must install the JDK and JCE files manually. Do you accept the Oracle Binary Code License Agreement [y/n] (y)? Downloading JDK from http://public-repo-1.hortonworks.com/ARTIFACTS/jdk-8u112-linux-x64.tar.gz to /var/lib/ambari-server/resources/jdk-8u112-linux-x64.tar.gz jdk-8u112-linux-x64.tar.gz... 100% (174.7 MB of 174.7 MB) Successfully downloaded JDK distribution to /var/lib/ambari-server/resources/jdk-8u112-linux-x64.tar.gz Installing JDK to /usr/jdk64/ Successfully installed JDK to /usr/jdk64/ Downloading JCE Policy archive from http://public-repo-1.hortonworks.com/ARTIFACTS/jce_policy-8.zip to /var/lib/ambari-server/resources/jce_policy-8.zip Successfully downloaded JCE Policy archive to /var/lib/ambari-server/resources/jce_policy-8.zip Installing JCE policy... Checking GPL software agreement... GPL License for LZO: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html Enable Ambari Server to download and install GPL Licensed LZO packages [y/n] (n)? y Completing setup... Configuring database... Enter advanced database configuration [y/n] (n)? Configuring database... Default properties detected. Using built-in database. Configuring ambari database... Checking PostgreSQL... Running initdb: This may take up to a minute. Initializing database ... OK About to start PostgreSQL Configuring local database... Configuring PostgreSQL... Restarting PostgreSQL Creating schema and user... done. Creating tables... done. Extracting system views... .........ambari-admin-2.6.2.0.155.jar .. Adjusting ambari-server permissions and ownership... Ambari Server 'setup' completed successfully.Ambari server の起動
[root@84c36c37ff46 /]# ambari-server start Using python /usr/bin/python Starting ambari-server Ambari Server running with administrator privileges. Organizing resource files at /var/lib/ambari-server/resources... Ambari database consistency check started... Server PID at: /var/run/ambari-server/ambari-server.pid Server out at: /var/log/ambari-server/ambari-server.out Server log at: /var/log/ambari-server/ambari-server.log (中略)
コンテナのIPアドレスを調べて、8080 ポートでアクセスしてみます。
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' 84c36c37ff46 172.17.0.2
- 投稿日:2019-12-24T21:16:27+09:00
DockerなLaravel6でHMR(Hot Module Replacement)
はじめに
Laravelでvue.jsを使った開発をするときは
laravel-mix付属のホットリロード機能(HMR)が便利です。ただ、ホストで動かすことが想定されており、dockerベースでHMRな開発環境を作ろうとして戸惑ったので情報を整理します。
概要
Laravel6 + vue.js on Docker な環境でHMRをする設定を説明します。
Laradockは使いません。ホスト側にバージョン管理が必要なツールはあまり入れたくないため、すべてDockerコンテナ内で完結させる方針です。
(yarnの実行はコンテナ内で実行すると遅いのですが、Ubuntu 上ではそれほど違いが感じられなかったため、コンテナ内で行う場合について記載しております。)環境
- Ubuntu 18.04
- Docker 19.03.5
- docker-compose 1.25.0
- Laravel 6
結論
- WebサーバーはLaravelのビルドインサーバーを使う
- PHPとNode.jsは同じコンテナに設定
- Webpackのプロキシ機能で8080のアクセスを8000ポート(ビルドインサーバー)に転送
ソースはこちら
https://github.com/odaryo/laravel6_vuejs_HMR環境構築について
ディレクトリ構成
┬- docker (Dockerの設定) │ └- app │ ├- conf.d (php設定ファイル) │ │ ├- php_settings.ini │ │ └- xdebug.ini │ └- Dockerfile ├- src (Laravelディレクトリ) └- docker-compose.ymlDocker環境
docker-compoe
appコンテナにはLaravelのビルドインサーバーを起動する設定を記載しています
docker-compose.ymlversion: '3' services: # laravel app: build: context: ./ dockerfile: ./docker/app/Dockerfile depends_on: - db links: - db volumes: - ./app:/app:cached - ./docker/app/conf.d/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini - ./docker/app/conf.d/php_settings.ini:/usr/local/etc/php/conf.d/php_settings.ini command: bash -c "php artisan serve --host 0.0.0.0" # 起動時にビルドインサーバーを起動 ports: - "8000:8000" # php artisan serve 用のポート - "8080:8080" # HMR用のポート # DB (mysql) db: image: mysql:8 environment: - MYSQL_DATABASE=testdb - MYSQL_USER=testuser - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=root - TZ=Asia/Tokyo restart: always volumes: - dev_mysql_data:/var/lib/mysql ports: - "33306:3306" volumes: dev_mysql_data: driver: localDockerfile
PHPコンテナとNode.jsコンテナに分けるとうまく行かなかったため、マルチステージビルドの形でPHPコンテナにNode.jsを追加する
Dockerfile# Nodeイメージ FROM node:13-alpine as node # PHPイメージ FROM php:7.3-alpine # Laravel環境に必要なパッケージをインストール RUN apk update \ && apk upgrade \ && apk add --no-cache \ bash \ git \ unzip \ libpng \ libpng-dev \ libjpeg \ icu \ icu-dev \ icu-libs \ libxml2 \ libxml2-dev \ openssl \ openssl-dev \ && docker-php-ext-install \ pdo_mysql \ mysqli \ gd \ mbstring \ intl \ xml \ opcache \ && docker-php-ext-enable intl mbstring \ && apk --update --no-cache add autoconf g++ make \ # xdebugインストール && pecl install -f xdebug \ && docker-php-ext-enable xdebug \ && apk del --purge autoconf g++ make # Composerのインストール RUN curl -sS https://getcomposer.org/installer | php ;mv composer.phar /usr/local/bin/composer; RUN composer global require hirak/prestissimo \ && composer global require phpunit/phpunit # Nodeコンテナからyarnとnodeをコピー COPY --from=node /opt/yarn-v* /opt/yarn COPY --from=node /usr/local/bin/node /usr/local/bin/ # 使いやすいようにシンボリックリンク作成 RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \ && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg # ホスト側とuser_id, group_idを合わせる ARG USER_ID ARG GROUP_ID RUN addgroup -g ${GROUP_ID} -S app-user && \ adduser -u ${USER_ID} -S app-user -G app-user USER app-user:app-user # Setup working directory WORKDIR /app参考
構築手順
docker起動
$ docker-compose build $ docker-compose up -dlaravelインストール
appコンテナに入って実行します。
※laravelインストールまではコンテナが起動しないため、runコマンドで実行する必要があります。$ docker-compose run app /bin/bash $ composer create-project --prefer-dist laravel/laravel .vue.jsを使う設定
laravel 6.0からデフォルトではインストールされないため、laravel/uiからインストールします。
$ composer require laravel/ui --dev $ php artisan ui vue $ exitここでdockerを再起動しておく
$ docker-compose up -d
http://localhost:8000
にアクセスして、スタート画面が表示されたらOK
node_moduleのインストール
$ docker-compose exec app yarnHMRの設定
ホットリロードの確認用に、welcome.blade.phpのbody内にVue.jsのComponentを追加しておく
Componentはインストール時に作成されるサンプルを使用welcome.blade.php<div id="app"> <example-component></example-component> </div> <script src="{{ mix('js/app.js') }}"></script>webpack.mix.jsにHMRの設定を追加
8080へのアクセスではエラーとなり
Cannot GET /
が表示されてしまうため、ビルドインサーバーの8000ポートへプロキシしてやりますwebpack.mix.jsmix.webpackConfig({ devServer: { host: '0.0.0.0', port: 8080, proxy: { '*': 'http://0.0.0.0:8000' }, // Windows(Docker for windows)の場合は下記を追加する // watchOptions:{ // aggregateTimeout:200, // poll:5000 // }, } });ホットリロードの実行
$ docker-compose exec app yarn hot
http://localhost:8080
へアクセスして、コンポーネントの内容が表示されれば完了です。
コンポーネントを修正して、変更が反映されることを確認しましょう。参考
終わりに
ホットリロードでフロントエンド開発が捗ります。
注意点としては、HMRで監視するファイルはJavascriptやCSSなので、blade自体を更新した場合はブラウザの更新が必要となります。
- 投稿日:2019-12-24T18:30:21+09:00
Cloud RunでnltkライブラリがインストールされたDockerコンテナをデプロイする方法
Cloud RunでnltkライブラリがインストールされたDockerコンテナをデプロイしようとしてハマったのでメモ。
事象
以下のように、
Dockerfile
にnltk
ライブラリをインストールするよう記述し、Cloud RunでDockerコンテナをデプロイしようとするとエラーが発生します。DockerfileRUN pip install nltk RUN nltk.download('punkt')error2019-12-23 14:02:28.321 PDT Resource [93mpunkt[0m not found. 2019-12-23 14:02:28.321 PDT Please use the NLTK Downloader to obtain the resource: 2019-12-23 14:02:28.321 PDT 2019-12-23 14:02:28.321 PDT [31m>>> import nltk 2019-12-23 14:02:28.321 PDT >>> nltk.download('punkt') 2019-12-23 14:02:28.321 PDT [0m 2019-12-23 14:02:28.321 PDT For more information see: https://www.nltk.org/data.html 2019-12-23 14:02:28.321 PDT 2019-12-23 14:02:28.321 PDT Attempted to load [93mtokenizers/punkt/PY3/english.pickle[0m 2019-12-23 14:02:28.321 PDT 2019-12-23 14:02:28.321 PDT Searched in: 2019-12-23 14:02:28.321 PDT - '/home/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/local/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/local/share/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/local/lib/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/share/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/local/share/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/lib/nltk_data' 2019-12-23 14:02:28.321 PDT - '/usr/local/lib/nltk_data' 2019-12-23 14:02:28.321 PDT - ''errorメッセージを読むと、「
nltk
ライブラリのダウンロード先を探してみたけど見つからない!」と言っているようです。
Dockerfile
でnltk
ライブラリをインストールしているにも関わらず不思議なエラーです。原因
以下でヒントが述べられています。
https://stackoverflow.com/questions/58654672/after-adding-nltk-downloadwords-google-cloud-run
どうやら、nltk
のインストール方法には以下の3つが存在していて、App EngineやCloud Runで動作させるためには、Manual installationが必要とのこと。
- Interactive installer
- Command line installation
- Manual installation
nltk.download()
はInteractive installer(GUIを使う)なのでApp EngineやCloud Runで動作しなかったようです。解決策
Dockerfileに以下を追記することでnltkライブラリを使えるようになりました。
# for install wget RUN apt-get update && apt-get install -y wget # for NLTK manual download WORKDIR /usr/local/nltk_data/tokenizers RUN wget "https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/packages/tokenizers/punkt.zip" -O punkt.zip RUN unzip punkt.zip
- 投稿日:2019-12-24T17:46:23+09:00
Docker Compose 入門
Docker Compose とは
Docker Composeは、複数のコンテナで構成されるアプリケーションについて、Dockerイメージのビルドや各コンテナの起動・停止などをより簡単に行えるようにするツールです。
これまでの連載の中でも、複数のコンテナを起動させる場面がありました。その際、各コンテナを起動するために、それぞれ起動コマンドを実行する必要がありました。また、コンテナを起動する際に、いろいろなオプションを利用していました。コマンドや手順が複雑になると、他の環境で使う/使ってもらう場合に、ミスが発生しやすくなります。他の環境でも同じ構成(同じDockerイメージ)で動かせるというDockerのメリットを生かすには、起動手順なども簡単であってほしいですよね。
そこで活躍するのが、Docker Composeです。
Docker Composeでは、Dockerビルドやコンテナ起動のオプションなどを含め、複数のコンテナの定義をymlファイルに書き、それを利用してDockerビルドやコンテナ起動をすることができます。一つの簡単なコマンドで複数のコンテナを管理できるようになります。
要は 複数のコンテナを連携させたいときに楽しようよ っていうものです。
Docker Compose 導入
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose $ sudo chmod +x /usr/local/bin/docker-compose $ sudo gpasswd -a $USER docker $ docker-compose -v docker-compose version X.XX.X, build XXXXXXXX動かしてみよう
拙作の
docker-compose.yml
を実際に動かしてみたいと思います。
$ git clone https://github.com/iedred7584/DockerWordpressPlayground.git $ cd DockerWordpressPlayground/ $ docker-compose up -d Creating mysql_playground ... done Creating wordpress_playground ... done Creating pma_playground ... done
docker-compose
コマンドを使います。
up
でdocker-compose.yml
をもとに作成します。
-d
で作成が完了して起動した際に自動でデタッチしてターミナルを返すようにします。Creating mysql_playground ... done Creating wordpress_playground ... done Creating pma_playground ... doneと出力されれば成功です。
詳細な解説
docker-compose.ymlversion: "3" services: mysql: image: mysql:5.7 container_name: mysql_playground ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: docker MYSQL_DATABASE: playground MYSQL_USER: docker MYSQL_PASSWORD: docker command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_bin wordpress: image: wordpress:latest container_name: wordpress_playground volumes: - ./wordpress/wwwroot:/var/www/html - ./wordpress/backup:/tmp/backup - ./wordpress/log:/tmp/log ports: - 50080:80 depends_on: - mysql environment: WORDPRESS_DB_NAME: playground WORDPRESS_DB_USER: docker WORDPRESS_DB_PASSWORD: docker links: - mysql:mysql phpmyadmin: image: phpmyadmin/phpmyadmin container_name: pma_playground ports: - 50081:80 depends_on: - mysql environment: PMA_HOST: mysql PMA_PORT: 3306 PMA_USER: docker PMA_PASSWORD: docker links: - mysql:db
version
はdocker-compose.yml
がどのバージョンから対応させているのかを指定します。投稿日時点では3.7
が最新です。(https://docs.docker.com/compose/compose-file/)
services
はそのdocker-compose.yml
で動かすものの指定です。services
以下にmysql
wordpress
phpmyadmin
がありますが、これは$ docker run mysql $ docker run wordpress $ docker run phpmyadminと意味合いでは同じものです。
各services
以下の
image
は使用するイメージ
container_name
は作成したコンテナの名称を指定します。
ports
は作成したコンテナで使用するポートを指定します。
depends_on
はここで指定したコンテナが起動するのを待ってからこのコンテナを作成する指示します。
environment
は環境変数の宣言します。
links
はコンテナ間連携で連携させるコンテナ名を指定します。Docker Compose と Dockerfile の連携
docker-compose.ymlversion: "3" services: sample: build: .
build
でDockerfile
を指定します。これはdocker-compose.yml
から見たDockerfile
の場所を指定します。まとめ
$ docker run --env xxxx=xxxx --env xxxx=xxxx mysql $ docker run --env xxxx=xxxx --env xxxx=xxxx wordpressとかつらい思いをせずに Docker Compose を使いましょう。
- 投稿日:2019-12-24T15:56:16+09:00
AlpineLinux on Docker × Laravelが遅い話
開発環境だけアプリがめちゃくちゃ重い
新しいチームに配属されて最初の感想です。
本番ではめちゃめちゃ早いのに開発環境がむちゃくちゃに遅い。
DXが最悪だったので原因を調査したところAlpineからCentOSに乗り換えたら3倍ぐらい早くなったという話です。
ベンチマーク
長々した説明は置いといて、結論から言うと「よくわからないがdockerで動かしているphpが異常に重い」ということだけがわかった。
これアプリケーションコードが重いというわけではなく、Alpine Linux on Docker で動いているLaravelが全体的に処理が遅いという体感があったので実際に計測してみた。
以下のPHPコードをAlpineとCentOSで動かして比較してみる。CentOSのDockerfileはPHP入れる処理だけなので省略。
test.php<?php ini_set('memory_limit', '-1'); function benchmark($i) { if($i ==! 0) { benchmark($i - 1); } function() { return sha1("test${i}");}; function() { return md5("test${i}");}; } $time_start = microtime(true); foreach (range(1, 10000) as $i) { benchmark($i); } $time = microtime(true) - $time_start; echo "{$time} sec";コードの内容は至って簡単で、メモリ上限を無くし再帰的処理でハッシュを取得していくだけ。
/t/test docker run --rm -v /tmp/test/test.php:/test.php centos-php php /test.php 8.2858350276947 sec /t/test docker run --rm -v /tmp/test/test.php:/test.php centos-php php /test.php 8.2832388877869 sec /t/test docker run --rm -v /tmp/test/test.php:/test.php centos-php php /test.php 8.2994618415833 sec/t/test docker run --rm -v /tmp/test/test.php:/test.php php:7.4.1-fpm-alpine php /test.php 8.1350269317627 sec /t/test docker run --rm -v /tmp/test/test.php:/test.php php:7.4.1-fpm-alpine php /test.php 8.0924451351166 sec /t/test docker run --rm -v /tmp/test/test.php:/test.php php:7.4.1-fpm-alpine php /test.php 8.0950059890747 secこれは特に問題なさそう(むしろAlpineの方が早い
Laravelでのベンチマーク
実際にLaravelで測定した時の結果を貼っていく
用意が面倒だったので開発環境での弊社サービスのLPページを測定対象とする
alpineでのdockerfileは以下の通り
FROM php:7.3-fpm-alpine ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so php RUN apk --no-cache update \ && apk add --no-cache $PHPIZE_DEPS postgresql-dev libpng-dev libjpeg-turbo-dev icu-dev \ && docker-php-ext-configure gd --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ \ && docker-php-ext-configure intl --enable-intl \ && docker-php-ext-install exif pdo_pgsql gd intl opcache pcntl RUN pecl install xdebug \ && docker-php-ext-enable xdebug WORKDIR /var/www/html対してCentOSベースのDockerfileは以下の通り
FROM centos:7.5.1804 #locale 追加 RUN sed -i -e '/override_install_langs/s/$/,ja_JP.utf8/g' /etc/yum.conf RUN curl -sL https://rpm.nodesource.com/setup_10.x | bash - \ && yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm \ https://s3-ap-northeast-1.amazonaws.com/sen-infra/rpms/centos/7/x86_64/pgdg-centos10-10-2.noarch.rpm \ && yum install -y postgresql10 \ nodejs \ zlib-devel \ glibc-common \ make \ libpng-devel \ cronie \ && yum install -y --enablerepo=remi,remi-php73 \ php \ php-opcache \ php-mbstring \ php-pdo \ php-pecl-memcache \ php-pecl-memcached \ php-pecl-redis \ php-pecl-imagick \ php-mcrypt \ php-mysqlnd \ php-xml \ php-gd \ php-devel \ php-pgsql \ php-pecl-ssh2 \ php-process \ php-intl \ php-pear \ php-pecl-apcu \ php-pecl-apcu-bc \ php-pecl-zip \ php-fpm \ && rm -rf /var/cache/yum/* \ && yum clean all COPY ./php-fpm.conf /etc/php-fpm.conf RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin \ && mv /usr/local/bin/composer.phar /usr/local/bin/composer \ && composer global require hirak/prestissimo WORKDIR /var/www/html CMD ["/usr/sbin/php-fpm","-F","-y","/etc/php-fpm.conf"]LPページなのでミドルウェアもプロパイダーも挟んではいない(はず
まずはAlpineから
yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.392s user 0m0.013s sys 0m0.004s yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.392s user 0m0.013s sys 0m0.000s yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.409s user 0m0.012s sys 0m0.004s平均して約0.4sec
対してCentOSベースでは
yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.064s user 0m0.007s sys 0m0.007s yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.062s user 0m0.003s sys 0m0.012s yoshiken@yskn:~/Git/sen/album$ time curl localhost real 0m0.052s user 0m0.014s sys 0m0.000s平均して0.06sec前後といったところ。
よくわからん
ここまで書いていてなんだが、何故PHPとしてのパフォーマンスが上のAlpineがLaravelを介すると急激に遅くなるかわかっていない。
調べた感じAlpineだと遅いという点では色々困ってる人が見当たってるが、具体的な解決策、根本原因は未だ解明されていない。
[5.2] Slow response times running within a php-7 Docker container · Issue #12228 · laravel/framework
php - PHP7 + Laravel +Nginx is terribly slow on Docker - Stack Overflow
AlpineでPythonが遅くなるのはAlpineの独自パッケージによる差分が原因とされているが、PHPでは最初のベンチマークの時点では大きな乖離がなかった。
https://superuser.com/questions/1219609/why-is-the-alpine-docker-image-over-50-slower-than-the-ubuntu-image言いたいこと
いろんな記事で「DockerとAlpine使って爆速で環境構築!!!」という文章を見かけるが、一旦待ってほしい。
ローカル環境では急速なスケールをする必要もないのでイメージが大きかろうが特に問題は無いわけであって、重要なのは使うフレームワークや言語に最適化された環境である。
もし、本番環境でコンテナ運用して開発と同じイメージ(特にAlpineLinux)を使用しているという状況であれば、まず一旦そのイメージの妥当性を確認すべきなのかなと。
他のディストリビューションをベースにした場合との速度計測をして正しい技術選定を行った方がよいのではと思う次第。
- 投稿日:2019-12-24T15:51:50+09:00
Dockerfile の Multi Stage Build 入門
Dockerfile おさらい
FROM nginx:latest ADD . /usr/share/nginx/htmlこういうやつです。
今回のサンプル
この記事で実際に動かすサンプルは Golang で書いた HTTP サーバーです。
main.gopackage main import ( "encoding/json" "net/http" "time" ) func echo(w http.ResponseWriter, r *http.Request) { type Response struct { Status int `json:"status` Time time.Time `json:"time` Message string `json:"message` } w.Header().Set("Content-Type", "application/json") res := &Response{ Status: http.StatusOK, Time: time.Now(), Message: "Json API Server by Golang on Docker", } json.NewEncoder(w).Encode(res) } func main() { http.HandleFunc("/", echo) http.ListenAndServe(":8888", nil) }今回の作業ディレクトリは
~/docker_msb/
とします。
~/docker_msb/main.go
に上記のコードをコピーしてください。Dockerfile を書く
Dockerfile は
~/docker_msb/dockerfile
とします。MultiStageBuild を使わない場合
# Golang 開発環境のイメージを使用 FROM golang:alpine # 作業ディレクトリを /sample に指定(存在しない場合は勝手に作成される) WORKDIR /sample # main.go を作業ディレクトリにコピー ADD main.go main.go # コンテナの8888番ポートを開放 EXPOSE 8888 # コンテナが起動する際に実行されるコマンド # go run main.go CMD ["go", "run", "main.go"]この Dockerfile を作成します。
$ docker build -t docker_s .このコマンドでイメージを作成します。
今回は比較のためにイメージを作成しておきます。MultiStageBuild を使う場合
# Golang の開発環境のイメージを build という名前をつけて使用 FROM golang:latest as build # 作業ディレクトリを /build に指定(存在しない場合は勝手に作成される) WORKDIR /build # main.go を作業ディレクトリにコピー ADD main.go main.go # Go で main.go をビルド RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o webapp -ldflags "-s -w" main.go # 空のイメージを使用 FROM scratch as release # 上の build から /build/webapp を /webapp にコピー COPY --from=build /build/webapp /webapp # コンテナの8888番ポートを開放 EXPOSE 8888 # コンテナが起動する際に実行されるコマンド # /webapp ENTRYPOINT ["/webapp"]この Dockerfile を作成します。
$ docker build -t docker_m .このコマンドでイメージを作成します。
今回は比較のためにイメージも作成しておきます。比較
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker_s latest dbfcaa92a174 9 seconds ago 359MB # 使用しない場合 docker_m latest d2a160b3620f 49 seconds ago 5.57MB # 使用した場合
size
を見ると一目瞭然ですね。
MultiStageBuild を使用しない場合のサンプルではgolang:alpine
を使用しているため360MBですが、golang:latest
は800MBもあります。 MultiStageBuild を使用したほうがストレージの容量を圧迫せずに済みます。まとめ
MultiStageBuild が使えるときは使おう。
- 投稿日:2019-12-24T15:43:00+09:00
docker-compose 停止 + イメージ削除 + ボリューム削除
docker-composeで起動
docker-compose up --build -dhttp://docs.docker.jp/compose/reference/up.html
docker-composeで停止 + イメージ削除 + ボリューム削除
docker-compose down --rmi all -vオプションの意味は公式サイト参照
http://docs.docker.jp/compose/reference/down.html
- 投稿日:2019-12-24T15:18:38+09:00
macOSでDockerを使ったGoのアプリケーション開発を爆速にするホットリローダを作った
はじめに
メリークリスマス!!
みなさんは Go のアプリケーション開発をどのような環境で行っていますか?
弊社ではゲームのアプリケーションサーバに Go を採用しており、開発は macOS で Docker for Mac を利用しています。開発当初はこの構成による不満は特に感じていませんでしたが、1年半ほど経ってプロジェクトの規模が大きくなったことで、無視できないレベルで開発スピードを低下させる要因となってしまいました。
弊社ではアプリケーション開発にソースコードの自動生成を多用しており、その影響もあってかコードベースの Go のコードは 150万行を超える規模になっています。 加えて、ビルドする際は cgo 経由で利用している C++ のコードもそれなりの量絡んでくることもあり、 Docker for Mac を使った Docker コンテナ上でのビルドに要する時間は、 メモリ8GB, 6CPUを割り当てたコンテナにも関わらず 5分を超える時間がかかっていました。 ( それでもまだ良い方で、他の方のマシンスペックでは 10分程かかる場合もあったようです )実際はビルドキャッシュが効くので毎回 5 ~ 10 分かかるわけではありませんが、パッケージの依存関係によっては数珠つなぎ的に再ビルドが必要になってしまうケースもあるので、一文字編集したら 10 分待つという状況も起こり得ます。
このままではとても開発していられないということで、ビルドを爆速にするツールを開発してみました。
この記事では、開発したツールの紹介と、ちょっとトリッキーな実装をしているので、どうやって実現したかという話にも触れたいと思います。ホットリローディング
ウェブアプリケーションなどを開発する際、ファイルの追加・更新・削除といったイベントを契機に自動で再ビルド・実行する仕組みを利用することが多いと思います。これらはホットリローディングやライブリローディングなどと呼ばれたりしますが、もちろん Go にも存在します。
有名どころだと https://github.com/gravityblast/fresh や https://github.com/oxequa/realize が挙げられますが、どちらも現在メンテされてはおりません...。弊社では、上で挙げた https://github.com/gravityblast/fresh を利用していました。
また、ホットリローディングを Dockerコンテナ 上で動作するアプリケーションで行うため、以下のような設定を行っていました。docker-compose.yml
version: '2' services: app: image: golang:1.13.5 container_name: app volumes: - '.:/go/src/app' working_dir: /go/src/app environment: GO111MODULE: "on" command: | go get -u github.com/pilu/fresh && freshつまり、
volumes
でビルドに必要なソースコードが置かれているディレクトリをまるっとコンテナにマウントし、ホットリローダ (fresh
) をコンテナ上にインストールしてファイル監視を始めます。これによって、ローカル上のファイルを編集した場合でも、その変更がコンテナにも伝わり、
コンテナ上で動作しているホットリローダがそれを検知してアプリケーションを再ビルドし、無事ビルドできたら現在動いているアプリケーションと入れ替えます(リスタート)。仕組み自体はとてもシンプルなものなので、再実装も難しくはありません。
ただ今回改善したいのは ビルド時間 なので、コンテナの上でビルドしているうちは改善できません。
そこで次のようなツールを開発しましたrebirth
rebirth という Go のための ホットリローダを開発しました。
既存のホットリローダと大きく異なるのは、 Docker コンテナ上でのビルドを避けるために
ホスト上でクロスコンパイルしつつDockerコンテナで動くアプリケーションをホットリロードできる 機能を持っている点です。
これによって、 Docker for Mac に依存せずにホストマシンの力を使い切ってビルドできるようになります。
( ホスト上でビルドするようにした結果 5分かかっていたビルド時間が 30 秒ほどに減り、目に見えて高速化しました )どのように使うかというと
. ├── docker-compose.yml ├── main.go └── rebirth.ymlこのような構成のワークスペースがあったとして、
docker-compose.yml
が以下のような内容だとします。 ( 先に挙げたdocker-compose.yml
中のfresh
の部分がtail -f /dev/null
になっているだけです )docker-compose.yml
version: '2' services: app: image: golang:1.13.5 container_name: app volumes: - '.:/go/src/app' working_dir: /go/src/app environment: GO111MODULE: "on" command: tail -f /dev/nullここで
docker-compose up -d
とすると、app
という名前のコンテナが立ち上がると思います。
ここで、rebirth.yml
を記述します。rebirth.yml
host: docker: app
host.docker
にホットリロードしたいアプリケーションのあるコンテナの名前を書きます。次に、以下を実行して
rebirth
という CLI をインストールします。$ GO111MODULE=on go get -u github.com/goccy/rebirth/cmd/rebirthこれで準備完了です。 macOS上で
rebirth
を実行します$ rebirth # ホットリローダが立ち上がる。 # ファイルを編集すると、 app コンテナ上のアプリケーションがビルド後のものに入れ替わる以上になります。
...ここで
!?
と思っていただけたら嬉しいのですが
コンテナ上に何もインストールしていないのに、 macOSにインストールしたバイナリのみでコンテナ上のアプリケーションのホットリロードを実現する というのがこのツールを作った時のこだわりポイントでした。これによって、Docker を使わない場合と使い方を変えることなく利用することができるようになっています (
rebirth.yml
の書き方を変えるだけ )続いて、これをどう実現しているかについて触れていきます
実装
流れが少し複雑なので、図を使って説明していきます。
はじめに、下の図を見てください。一番外の大きい枠が macOS 上だということを表現しています。
その上にあるグレーのlinux
と書かれている部分は、
Docker for Mac を使って動作している linux コンテナを表しています。破線で囲われている中は、
volumes
でマウントされていることを表しています。
( つまり、 workspace が~/work/app
という状況でdocker-compose.yml
のvolumes
に.:/go/src/app
と書かれている状態になります )1.
rebirth
をインストールするまず、
GO111MODULE=on go get -u github.com/goccy/rebirth/cmd/rebirth
でrebirth
CLI をインストールします2.
rebirth
自身のクロスコンパイル
rebirth
を実行した際にはじめに行うのは、ターゲットとなる Dockerコンテナのアーキテクチャ向けに自分自身をクロスコンパイルし、__rebirth
という名前で~/work/app
直下の.rebirth
ディレクトリ配下に置きますコンパイル対象のアーキテクチャを知るため、 https://godoc.org/github.com/docker/docker/client を利用して docker remote API 経由で
go env GOOS
とgo env GOARCH
を実行しています。3. コンテナ上にクロスコンパイルした rebirth バイナリを配置する
.rebirth
があるディレクトリは~/work/app
直下なので、コンテナ上にマウントされています。
このため、自動でコンテナ上の/go/src/app/.rebirth
配下にコンテナ上で実行可能な__rebirth
バイナリが配置されます。( このあたりは、マウントを利用せずともバイナリを直接コンテナ上にコピーすれば同じことができますが、大抵の場合はマウントを前提としても問題ないと思っているので、処理を簡単にするためにこのようにしています )
4. アプリケーションのファイルを監視し始める
図では
main.go
のファイルイベントを監視し始めることを表しています
( 実装には fsnotify を使っています )5. アプリケーションコードをクロスコンパイルする
rebirth
自身をクロスコンパイルしたときと同じ要領で、コンテナのアーキテクチャ向けにクロスコンパイルします。
少し違うのは、アプリケーションコードがcgo
を利用したものであったとしてもコンパイルできるようにしなければらない点です。このため
GOOS
やGOARCH
の指定に加え、CGO_ENABLED=1
を有効にします。
さらに、 C/C++ コードを macOS 上で linux 向けにコンパイルできるよう、クロスコンパイラを作らなければいけません。 https://github.com/FiloSottile/homebrew-musl-cross にあるように$ brew install FiloSottile/musl-cross/musl-crossでインストールをお願いします。 ( ビルドに30分程度かかります )
( 参考 : https://qiita.com/keijidosha/items/5f4a68a3341a44a25ab9 )
また、今までコンテナ上のパスとして設定されていた
GOPATH
が、ホスト上で指定されたものに変わるため、
振る舞いを揃えるために、ワークスペースに作った.rebirth
ディレクトリをGOPATH
の起点として扱い、go.mod
のモジュール名を見ながら、アプリケーションコードを.rebirth
配下に配置し直してビルドを行っています。例えば、
go.mod
にmodule github.com/company/webapp
と書かれていたとすると、
.rebirth/src/github.com/company
というディレクトリを掘る- ワークスペース (
webapp
ディレクトリ ) への symlink を 1 で作ったディレクトリの配下に作る.rebirth/src/github.com/company/webapp
へ移動するGOPATH=/path/to/.rebirth go build ...
のようにGOPATH
を変更してビルドするというようなことを行います。これによって、依存モジュールなどをアプリケーションワイドにインストールすることが可能なので、
ローカルの GOPATH と混ざったりすることはなくなります。上記をまとめると、以下のような環境変数やオプションをつけて
go build
を実行しています$ GOPATH=.rebirth GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-cc CXX=x86_64-linux-musl-c++ go build --ldflags '-linkmode external -extldflags "-static"'( ※ 実際には クロスコンパイラへのパスを通すために
PATH
に追加したり、 GOPATH も絶対パスで表現したりしています )6. コンテナ上にクロスコンパイルしたアプリケーションバイナリを配置する
ビルドした結果は、
.rebirth
配下にprogram
という名前で配置しています。
__rebirth
のときと同様に、マウント先のコンテナ上に自動的に配置されます。7.
__rebirth
の実行コンテナ上で
__rebirth
バイナリを実行します。このとき、動作しているのが Dockerコンテナ上であり、
かつrebirth.yml
にhost.docker
の指定がある場合には、ファイル監視を行わない専用のモードで起動します。
起動時に、自身の PID を記録したファイルを.rebirth
配下に書き出します。( 自身がDockerコンテナ上で起動しているかどうかは、
/.dockerenv
が存在するかで判定することができます )8. コンテナ上でアプリケーションを起動する
__rebirth
から.rebirth/program
を実行します9. ファイルの変更
main.go
をホスト上で編集します10. ファイルの変更を検知
ホスト上の
rebirth
プロセスがmain.go
の更新を検知します11. アプリケーションの再ビルド
5 で説明したことをもう一度行います
12. アプリケーションの配置
6 で説明したことをもう一度行います
13. アプリケーションの再起動要請
ホスト上の
rebirth
から コンテナ上の__rebirth
へアプリケーションの再起動要請を行います。
実装には、事前に書き出しておいた__rebirth
プロセスの PID をもとに、SIGHUP
を送ることで実現しています。本当は
PID
をファイルを経由せずに取得したかったのですが、 docker remote API を経由して知ろうとすると、ホスト上のPID名前空間で表現された値しかとれないため実現できませんでした ( コンテナ上でps
したときとは別の PID が返ってくる )14. アプリケーションの再起動
SIGHUP
を受け取った__rebirth
プロセスが、起動中のプロセスを停止して新しく配置されたprogram
を実行すれば再起動の完了ですおまけ
cgo を利用しているコードから、他の C ライブラリを参照している場合
例えば弊社では、 cgo で記述されたコードから、
zlib
を利用していました。
こういった場合は、別途libz.a
をクロスコンパイルする必要があります。ビルドした
libz.a
やzlib
のヘッダファイルを参照可能な場所に移して ( たとえば ワークスペース配下 )
rebirth.yml
に以下のように書けばそれを用いてシンボル解決してくれるようになりますhost: docker: app build: env: CGO_LDFLAGS: ./lib/libz.a # lib に置いたクロスコンパイル済みの libz.a を参照する CGO_CXXFLAGS: -I./include # include に置いた `zlib.h` などを参照する( 相対パスは、適宜ツール内部で絶対パスに置き換えて参照します )
ホットリロード以外の機能
ホットリロードをクロスコンパイルで行うようになると、今までコンテナ上で行っていたテストやスクリプトの実行などなどを同じ手段で行いたくなると思います。
そこで
rebirth
ではgo build
,go test
,go run
をクロスコンパイルしつつ実行してくれるコマンドを用意しています。それぞれrebirth build
,rebirth test
,rebirth run
をホスト上で実行していただければ、クロスコンパイルしつつ、必要であればその結果をコンテナ上で走らせてくれます。ぜひご活用くださいおわりに
macOS 上で Docker を利用しているときのビルドを高速化したい!というニーズから実装したツールですが、
普通にホットリローダとして使う場合においてもfresh
より使いやすくなっていると思いますので、ぜひお試しいただければ幸いです。引き続き改善しながら弊社で使っていこうと思っていますので、
なにか要望やバグを見つけた場合も気軽に報告いただければと思います。それではよいクリスマスをお過ごしください!!
- 投稿日:2019-12-24T14:16:41+09:00
ReactとNginxでリロードしても404しないSPAを作る
はじめに
いなたつアドカレの二十四日目の記事です。
Dockerを使ってReactで作ったアプリケーションをnginx上にポイ投げするためのあれですね。DockerってなにとかnginxってなにReactってなにってのはスルーで行きます。
今回の記事はcreate-react-app(以下CRA)を使用する前提となっています。つかうもの
- react
- react-router
- nginx
- docker
完成系
react-router使ってnginx上でリンクに#がつかないかつリロードしても404にならない構成の作成
ディレクトリ構成
- app
- docker
- react
- Dockerfile
- nginx
- default.conf
- web
- docker-compose.yml
こんなかんじです
dockerディレクトリにdockerで使用するファイルを格納しています。
webディレクトリにCRAで作成したアプリが入りますね。DockerでReact
docker-compose.ymlversion: '3' services: react_app: container_name: react_app build: ./docker/react command: npm start volumes: - ./web:/app ports: - 3000:3000つづいて
docker/react/DockerfileFROM node WORKDIR /appここは基本的になんでも構いません(めんどくさかった)
作成するアプリケーションに合わせてpackage.jsonなどを用意してあげてください。
今回はめんどくさいのでこれでいきます。Dockerで んぎっくす
はい、Nginxいきます
docker-compose.ymlnginx: image: nginx container_name: nginx ports: - 8080:80 volumes: - ./web/build:/var/www - ./docker/nginx/:/etc/nginx/conf.d/ depends_on: - react_appweb(CRAのディレクトリ)の中のbuildをnginxコンテナにマウントしています。
docker/nginx
には設定ファイルですね。default.confserver { listen 80; location / { root /var/www; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }buildをマウントしてそのマウントしたファイルのindex.htmlを表示するぜ〜って感じですね。
Reactのコンポーネント構成
App.jsimport React from 'react'; import { BrowserRouter as Router, Route } from 'react-router-dom' import Hello from './Pages/Hello' import Changed from './Pages/Changed' function App() { return ( <Router> <Route exact path='/' component={Hello} /> <Route exact path='/changed' component={Changed} /> </Router> ); } export default App;
react-router
で2つのページを遷移できるようにします。/Pages/Hello,jsimport React from 'react'; import {Link} from 'react-router-dom' const Hello = () => { return ( <div> <div>Hello</div> <Link to='/changed'>ぺーじせんい</Link> </div> ) } export default Hello;/Pages/Changed,jsimport React from 'react'; const Changed = () => { return ( <div> <div>Changed</div> </div> ) } export default Changed;ページ遷移するだけですね。
うごかしてみる
とりあえずreactのプロジェクトをbuildしましょう。
localhost:8080
にアクセスします。こんな感じですね。
ページ遷移してみます。
遷移できましたね。おっけーです。
ちょっとリロードしたくなってきたわ
あっあっあっ
だめですね。
んぎっくす
さんに怒られちゃいました。ハッシュルーターとかダサいじゃん?
URLに「#」とかつくHashルーターをつかうことでこれを解決することはできます。
localhost:8080/#/changed
だっっっっっっっっっさあああああいやだよ。
解決しよう
nginxの設定を少し変えましょう
```default.conf
server {
listen 80;location / { root /var/www; index index.html index.htm; try_files $uri /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; }}
```
try_files $uri /index.html;
この一行を書くだけですね。ほら、リロードしてみてくださいよ、怒られますか?怒られませんよね。
これで解決ですね。
なぜ解決できたのか
とりま解決でけたらえーねんって人はみなくてよきですえ
nginxにはtry_files ディレクティブというものが存在し、これは、引数を前から順番にファイルが存在するかをtryしまくって行ってくれます。そして見つからなかった場合は
index.html
を返却しましょうねっていう感じです。何も見つからなかったら404を返すよ〜って実装をする場合もありますね。
- 投稿日:2019-12-24T11:32:19+09:00
Laravel + PassportでAPIを作成する
Making an API with Laravel + Passport
以前の投稿を読んでいる場合は、Laravelプロジェクトのセットアップ方法を既に知っているので、それから続けて、パスポートをインストールしてJWT APIを作成します
If you have followed my previous posts, you already know how to setup a Laravel project, continuing from that, we will install passport to create an JWT Api
必要条件 / Requisites:
Laravel Project
Docker
Laravel/Passport
Laravel-Shovel
laravelプロジェクトフォルダー内に、一時的なdockerコンテナーを作成して「composer」を使用し、パスポートをインストールします
Inside the laravel project folder, we create a temporary docker container to use
composer
and install passportdocker run --rm -v $(pwd):/app composer require laravel/passportこの命令にはしばらく時間がかかります...完了したら、
docker-compose
を開始できますThis instruction will take a while ...once done, we can start the
docker-compose
sudo docker-compose up --build実行されると、「http:// localhost /」に移動して確認できます
Once its running, we can verify by going to
http://localhost/
新しい移行を実行し、パスポートをインストールします
We run the new migrations and Install passport
sudo docker-compose exec app-server php artisan migrate sudo docker-compose exec app-server php artisan passport:installこれで、お気に入りのエディター(私の場合はPHPStorm)を使用してプロジェクトを開き、
User
モデルを編集してHasApiTokens
特性を追加できます。We can now open the project using our favorite editor, in my case PHPStorm, and edit the
User
model to add theHasApiTokens
trait.<?php namespace App; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ];次に、
passport:routes
をAuthServiceProvider
に追加しますNext we add the
passport:routes
to theAuthServiceProvider
<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; use Laravel\Passport\Passport; class AuthServiceProvider extends ServiceProvider { /** * The policy mappings for the application. * * @var array */ protected $policies = [ // 'App\Model' => 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); // } }
config \ auth.php
でApi認証プロバイダーをパスポートに変更しますWe change the Api Auth provider to passport in the
config\auth.php
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', 'hash' => false, ], ],次に、
AuthController
を作成しますNow we create our
AuthController
docker-compose exec app-server php artisan make:controller API\\AuthControllerこの新しいコントローラーはdockerによって作成されたため、ファイルの書き込み許可を変更する必要がある場合があります
Since this new controller was made by docker, you might need to change the write permission of the file
sudo chown -R myUser:myUser mylaravelproject/エディターで新しいAuthControllerを開くことができます。関数については、itsolutionstuff.com
ただし、機能に若干の変更が加えられています
Now we can open the new AuthController in our editor, as for the functions, I took the example from the itsolutionstuff.com
But with some slight changes in the functions
<?php namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; use App\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; class AuthController extends Controller { /** * Register api * * @return \Illuminate\Http\Response */ public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email', 'password' => 'required|confirmed', 'password_confirmation' => 'required|same:password', ]); if($validator->fails()){ return response()->json(["error"=>$validator->errors()],422); } $input = $request->all(); $input['password'] = bcrypt($input['password']); $user = User::create($input); $success['token'] = $user->createToken('MyApp')->accessToken; $success['name'] = $user->name; return response()->json($success); } /** * Login api * * @return \Illuminate\Http\Response */ public function login(Request $request) { if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){ $user = Auth::user(); $success['token'] = $user->createToken('MyApp')-> accessToken; $success['name'] = $user->name; return response()->json([$success]); } else{ return response()->json(["error"=>"Unauthorized"],422); } } }
routes \ api.php
内にルートを追加しますAdd the routes inside
routes\api.php
Route::post('register', 'API\AuthController@register'); Route::post('login', 'API\AuthController@login');お気に入りのRESTクライアントを使用して、登録とログインをテストできます。「不眠症」を使用しています
You can test the register and login, using your favorite REST client, I'm using
insomnia
http://localhost/api/register
{ "name":"My Name", "email":"secremeail@email.com", "password":"securepassword", "password_confirmation":"securepassword" }http://localhost/api/login
{ "email":"secremeail@email.com", "password":"securepassword" }これで、基本的なAPIを実装しましたが、より良い応答をするために、以下を使用します。
With this, we have implemented our basic API, but to make a better response, we use:
より良いAPI応答を行うためのライブラリです。
Is a library to make better API responses.
インストールする前に、
docker-compose
を停止し、一時的なcomposer
コンテナを実行します。Before installing it, we stop
docker-compose
and run a temporarycomposer
container.docker run --rm -v $(pwd):/app composer require stephenlake/laravel-shovelミドルウェア内のルートをグループ化します
We group the routes inside the middleware
Route::group(['middleware' => ['ApiRequest',"ApiResponse"]],function (){ Route::post('register', 'API\AuthController@register'); Route::post('login', 'API\AuthController@login'); });すべてのAuthController応答を通常の応答に変更する必要があります
We have to change all the AuthController responses to normal resposes
<?php namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; use App\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; class AuthController extends Controller { public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email', 'password' => 'required|confirmed', 'password_confirmation' => 'required|same:password', ]); if($validator->fails()){ return response()->json(["messages"=>$validator->errors()],422) ->withMeta("message","Validation error"); } $input = $request->all(); $input['password'] = bcrypt($input['password']); $user = User::create($input); $success['token'] = $user->createToken('MyApp')->accessToken; $success['name'] = $user->name; return response($success); } public function login(Request $request) { $credentials = [ 'email' => $request->email, 'password' => $request->password ]; if (auth()->attempt($credentials)) { $token = auth()->user()->createToken('MyApp')->accessToken; return response(["token"=>$token]); } else { throw new \Exception("Invalid Credentials",422); } } }また、「app / Exceptions / handler.php」を処理する例外にjson応答を追加します
Also add a json response in the expection handled
app/Exepctions/handler.php
public function render($request, Exception $exception) { if($request->acceptsJson()) { return response() ->json(["messages"=>$exception->getMessage()],500) ->withMeta("message","internal server error"); } return parent::render($request, $exception); }このライブラリは、メタヘッダーを追加するすべてのAPI応答を標準化します
This library will standarize all api responses adding meta headers
{ "meta": { "code": 200, "status": "success", "message": "OK" }, "data": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiMTMzYzU3NjVlZmJjYTdmODQ0NDdlMTE4ZWUyZDc1YWI5YzY0MmQ3NTE2MjIzNWM1Y2FjNDNlNjI5ZDIyMzU1MzMzMzY1M2U2Yjc2ZTJhNzIiLCJpYXQiOjE1NzcxNTQwMzcsIm5iZiI6MTU3NzE1NDAzNywiZXhwIjoxNjA4Nzc2NDM3LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.KIoArr6X69eRLDluWWgEOS5gAlLmLxtYKhMURgzsmgmLooVV6EJDHGx11gnQ7_WmagtbredLabHXeSks6DQ2A8tGeqFyrVxnCRddAySxHDxhzF0VF2wF9rn_1OduDcC3xOVdrXPj-VkxToHLyW3e6A714XSTxHgzynEKBh2JtDIRN3lCt13_1F8iD9ocGHPLBrW-XFhV4Iw2atSyL8N5qQH29wsopwWZCoTqqAN2whgfylyCTlXFAcQWe0AOJEzc39jpTudkiSXAKKFuS1hCjLYYdiuae-NJGOTutDD3CzFjYrO-Kvq3-QBX7go4uNftOVwuARsvlBuyCfPbpzCM8FVfuZCEHMv7YODCrCs2s305PvsGAsIIcKB_9_dpx0nO-lZy9_Hsn6HKAztCkLBNQponLAM10pah36xaq5c_mOwRMIltRArfDi-QuteIP4XUYXJXDV96bw2BQGNlybbOU0z7x7ocLmlP7xh4NBZFjUs5eM02U6eJykewxr8UpIkoyi6N3-ZxKKhdIWfeW6jRFe7IHlKQ2QTR1Hp33zGPlwTIW8dTe8UYo_FXdQojsFV7uxc9GUUDFimrJT3cem6JvcUEWsQtbxRv3tyyMST4P5gZpr-bANS2z6DiseuQRjHU9ZNYX9rm62GS8Wo_sNXxbfayTrFk6mBsuNn33kY1T8o" } }
- 投稿日:2019-12-24T09:29:41+09:00
CesiumでSRTMの地形データを読み込ませてみる
概要
SENSYN Robotics(センシンロボティクス)の深見です。
主にWebアプリを中心に開発担当しており、最近はCesiumを利用したWebGL系の開発に関わっております。
本日は、その過程で得た、地形データをCesium上に反映させる方法につきまして紹介いたします。
CesiumについてはこちらCesiumに反映する地形データの取得
- Cesium上で地形データを反映させるためにはいくつか手段はありますが、ここではCesium World Terrain と同じく
quantized mesh
で配信する事例とします。- 今回利用する地形データはNasa SRTM(height map)を利用します。
- 地形データをダウンロード時には認証が求めらるので、こちらでログインIDを取得する必要があります。
- また、データ量が多いため、今回の対象は関東エリア周辺(N35E139.hgt)のみといたします。
Cesium Terrain Builder
- Cesium向けにterrainを生成するためcesium-terrain-builderを利用します。
地形データの作成
- cesium-terrain-builderを利用するため、docker imageを取得します。
$docker pull tumgis/ctb-quantized-mesh
- 事前にダウンロードしたheight map(N35E139.hgt)を配置します。
$ mkdir ~/terrain $ mv ~/Downloads/N35E139.hgt ~/terrain
- dockerコンテナを起動します。
$ docker run -it --name ctb -v ~/terrain/:/data tumgis/ctb-quantized-mesh
- gdalbuilderで仮想的なレイヤを定義するXMLファイル(vrt)を作成します。また、Cesium上では地形データをquantized meshで展開するため、ファイルを作成します。
$ gdalbuildvrt tiles.vrt *.hgt $ ctb-tile -f Mesh -C -N -o terrain tiles.vrt
- Cesiumが地形データを認識するための設定ファイルとして、layer.jsonを作成します。
$ ctb-tile -f Mesh -C -N -o -l terrain tiles.vrt
- 地形データとlayer.jsonが作成されているか下記コマンドで確認します。
$ tree -v -C -L 1 ~/terrain terrain/ |-- 0 |-- 1 |-- 2 |-- 3 |-- 4 |-- 5 |-- 6 |-- 7 |-- 8 |-- 9 |-- 10 |-- 11 |-- 12 `-- layer.json以上で地形データの作成は完了です。
地形データの配信
- webアクセスできる環境を用意します。配信する際にresponseヘッダーの確認が必要です。配信するサーバの環境に合わせ確認してください。
![]()
- Access-Control-Allow-Origin
- corsで許可して配信する場合には必要、上記の例は特別な理由がないので
Access-Control-Allow-Origin: *
としています。- Content-Encording
ctb-tile
のコマンドで作成された地形データはgzip形式で圧縮
されているため、配信する場合はContent-Encording: gzip
を指定します。- Content-Type
application/octet-stream
を指定します。cesium-terrain-builderで作成されたファイルが.terrain
の拡張子ファイルであり、一致するMIMEtypeが存在しないので、Content-Type: application/octet-stream
とします。- 今回はpythonのSimpleHTTPServerを利用してweb配信します。
$ python SimpleHTTPServer.py Serving HTTP on 0.0.0.0 port 8000 ...
Cesiumに地形データを読み込ませる
- CesiumのterrainProvidorにurlを設定します。
const viewer = new Cesium.Viewer('cesium', { terrainProvider : new Cesium.CesiumTerrainProvider({ url : 'http://localhost:8000/terrain' }), });ブラウザで確認
以上になります。
- 投稿日:2019-12-24T01:16:09+09:00
code-server オンライン環境篇 (5) Docker 上で、code-server を立ち上げる
これは、2019年 code-server に Advent Calender の 第15日目の記事です。
前回に続き、EC2 Instance を 立ち上げたいと思います。
目次
ローカル環境篇 1日目
オンライン環境篇 1日目 作業環境を整備する
オンライン環境篇 2日目 仮想ネットワークを作成する
オンライン環境篇 3日目 Boto3 で EC2 インスタンスを立ち上げる
オンライン環境篇 4日目 Code-Serverをクラウドで動かしてみる
オンライン環境篇 5日目 Docker 上で、code-server を立ち上げる
オンライン篇 6日目 自動化してみよう
オンライン篇 7日目 簡単な起動アプリを作成してみよう(オンライン上に)
...
オンライン篇 .. Coomposeファイルで構築
オンライン篇 .. K8Sを試してみる
...
魔改造篇はじめに
前回までで、boto3 x python で EC2 Instance を 立ち上げました。
そして、Code-Server を動かしました。今回は、Docker を利用して、Code-Serverを立ち上げてみましょう。
EC2 Instance を作る
前回のつづきから
$ git clone https://github.com/kyorohiro/advent-2019-code-server.git $ cd advent-2019-code-server/remote_cs04/ $ docker-compose build $ docker-compose up -dブラウザで、
http://127.0.0.1:8443/
を開く。Terminal 上で
Terminal$ pip install -r requirements.txt $ aws configure .. ..EC2Instance を 作成
$ python main.py --createEC2 情報を取得
$ python main.py --get >>>> i-0d1e7775a07bbb326 >>>> >>>> 3.112.18.33 >>>> ip-10-1-0-228.ap-northeast-1.compute.internal >>>> 10.1.0.228 >>>> {'Code': 16, 'Name': 'running'}SSHで中に入る
$ chmod 600 advent-code-server.pem $ ssh -i advent-code-server.pem ubuntu@3.112.18.33Docker を install
Docker 環境を作成していきます
EC2上で
$ sudo apt-get update $ sudo apt-get install -y docker.ioDocker の Hello World
$ sudo docker run hello-world atest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly.Code-Server を 起動してみよう
$ mkdir -p ${HOME}/.local/share/code-server/extensions $ sudo docker run -it -p 0.0.0.0:8080:8080 -p0.0.0.0:8443:8443 codercom/code-server:v2 --cert info Server listening on https://0.0.0.0:8080 info - Password is 86821ed9f02ef11d83e980da info - To use your own password, set the PASSWORD environment variable info - To disable use `--auth none` info - Using generated certificate and key for HTTPSできました!!
削除しよう
# ec2 instance から logout $ exit # local の code-server 上で $ python main.py --deleteなんども使い回したいならば、 ec2 instance を停止するようにしてください
※ 次回か次次回
次回
手動で行っていた作業を、自動化してあげましょう!!
コード
https://github.com/kyorohiro/advent-2019-code-server/tree/master/remote_cs03