- 投稿日:2021-01-19T22:47:47+09:00
gym.render() でDISPLAYのエラーが出たときの対処法
困ったこと
dockerコンテナでOpen AI Gymのサンプルコードを実行しようとしたら、次のようなエラーメッセージが出てしまいました。
ターミナルTraceback (most recent call last): File "hello_gym.py", line 8, in <module> env.render() File "/usr/local/lib/python3.6/dist-packages/gym/core.py", line 240, in render return self.env.render(mode, **kwargs) File "/usr/local/lib/python3.6/dist-packages/gym/envs/classic_control/cartpole.py", line 174, in render from gym.envs.classic_control import rendering File "/usr/local/lib/python3.6/dist-packages/gym/envs/classic_control/rendering.py", line 25, in <module> from pyglet.gl import * File "/usr/local/lib/python3.6/dist-packages/pyglet/gl/__init__.py", line 244, in <module> import pyglet.window File "/usr/local/lib/python3.6/dist-packages/pyglet/window/__init__.py", line 1880, in <module> gl._create_shadow_window() File "/usr/local/lib/python3.6/dist-packages/pyglet/gl/__init__.py", line 220, in _create_shadow_window _shadow_window = Window(width=1, height=1, visible=False) File "/usr/local/lib/python3.6/dist-packages/pyglet/window/xlib/__init__.py", line 165, in __init__ super(XlibWindow, self).__init__(*args, **kwargs) File "/usr/local/lib/python3.6/dist-packages/pyglet/window/__init__.py", line 570, in __init__ display = pyglet.canvas.get_display() File "/usr/local/lib/python3.6/dist-packages/pyglet/canvas/__init__.py", line 94, in get_display return Display() File "/usr/local/lib/python3.6/dist-packages/pyglet/canvas/xlib.py", line 123, in __init__ raise NoSuchDisplayException('Cannot connect to "%s"' % name) pyglet.canvas.xlib.NoSuchDisplayException: Cannot connect to "None"実行環境
実行環境は次のとおりです。
バージョン OS Ubuntu18.04 python 3.6 gym 0.18.0 pyglet 1.5.0 xvfb 2:1.19.6 x11vnc 0.9.13 Ubintu18.04ベースのdockerコンテナにvncを設定して、render()の出力結果をブラウザで確認しようとしています。
考えたこと
- VNCの設定ができていない
- X displayの設定ができていない
- gymサンプルコードのエラー(render周り)
- pygletのバージョンが合っていない(pygletは最新を入れていました)
gymのサンプルコードは次のとおりです。
sample.pyimport gym env = gym.make('CartPole-v0') env.reset() for _ in range(1000): env.render() env.step(env.action_space.sample()) # take a random action env.close()(実はこのコードは、別の理由でエラーが起こるのですが、それはまた別の記事で…)
原因
DISPLAYの設定ができていませんでした。
ターミナルで環境変数を確認します。
ターミナル$ printenv一覧がずらずらっと表示されて
ターミナル略 LANG=C.UTF-8 OLDPWD=/home/user VISIBLE=now USER=user PWD=/home/user/workspace HOME=/home/user 略
DISPLAY=
の表示がありません。設定したはずなのに…。DISPLAY 環境変数の設定 (うまくいかなかった例)
次の3つの方法は試しましたが、うまくいきませんでした。
解決策を早く知りたい方はジャンプ設定法1
もともとはDockerfileの中で設定していました(できているつもりでした)
DockerfileENV DISPLAY=:0でも、コンテナ内の環境変数は設定できていませんでした。
設定法2
docker run
のオプションで設定する次に試したのがこの方法。
ターミナルdocker run -e DISPLAY=$DISPLAY 以下略ちなみに、
-e
を--env
に変えても、だめでした笑設定法3
startup.sh
で設定する
startup.sh
は、ざっくり言うとコンテナを起動する際に、実行させることのできるシェルスクリプトです。
(よくわかっていないだけw)Dockerfileの最後に
CMD
命令を記述できますが、複数のコマンドを実行させたい場合に、
それらをstartup.sh
の中に記述することで実行させます。DockerfileCMD ["/startup.sh"]なぜ
startup.sh
を使ったかというと、ssh
とvnc
をsupervisord
によって起動させたかったので、/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
は少なくともbashで実行する必要があったからです。
そのため、コマンドを2行記述しました。startup.sh#!/bin/bash /usr/bin/supervisord -c /etc/supervisor/supervisord.conf export DISPLAY=:0結局この方法でも、エラーは解決しませんでした。
解決策
Dockerfileの中で次のように記述します。
RUN echo "export DISPLAY=:0" >> /etc/profileこれで、コンテナ内の環境変数にDISPLAYを設定することができました。
まとめ
dockerコンテナ内で
gym
を実行した際にpyglet.canvas.xlib.NoSuchDisplayException: Cannot connect to "None"
というエラーが表示されたら、
DISPLAYの環境変数が設定できていないかもしれません。Dockerfile内で
RUN echo "export DISPLAY=:0" >> /etc/profile
と実行すると設定できました。
- 投稿日:2021-01-19T22:23:59+09:00
Rust公式ドキュメントをDockerコンテナ上で進めてみる(2. 数当てゲームをプログラムする)
はじめに
タイトルの通りです。
前回:Rust公式ドキュメントをDockerコンテナ上で進めてみる(1. 事始め)参考にしたもの
Rust公式ドキュメント(TheRustProgrammingLanguage日本語版):https://doc.rust-jp.rs/book-ja/title-page.html
DokerHub(Rust):https://hub.docker.com/_/rust2.数当てゲームをプログラムする
公式ドキュメント:https://doc.rust-jp.rs/book-ja/ch02-00-guessing-game-tutorial.html
まず初めに全体を確認します。
以下ファイルの修正が必要そうなので、ローカルで作成したものをCOPYする方式をとります。
- src/main.rc
- Cargo.toml
コンテナにコピーするmain.rcとCargo.tomlを作成します。
なお、ローカルで作成するもののフォルダ構成。guessing_game - src/main.rc - Cargo.tomlguessing_game/src/main.rcextern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }guessing_game/Cargo.toml[package] name = "guessing_game" version = "0.1.0" authors = ["root"] edition = "2018" [dependencies] rand = "0.3.14"Dockerfileを作成します。一部、DockerHub上のサンプルに合わせています。
cargo init を使っているのは既にあるディレクトリでcargo newは使えないため。DockerfileFROM rust:latest WORKDIR /usr/src/projects COPY . . # 2.数当てゲームをプログラミングする RUN cargo init guessing_game --bin && \ cd /usr/src/projects/guessing_game && \ cargo build WORKDIR /usr/src/projects/guessing_game CMD ["cargo", "run"]ビルドしたところcargoのエラーになりました
$ docker build -t my-rust-app . (中略) ---> Running in 13aa45df5d36 error: `cargo init` cannot be run on existing Cargo packages The command '/bin/sh -c cargo init guessing_game --bin && cd /usr/src/projects/guessing_game && cargo build' returned a non-zero code: 101どうもcargo initは既存のディレクトリやソースを使うことはできてもCargo.tomlがあるとダメみたいです。
大人しく、cargo newしたあと、COPYで上書きする形にします。修正後のDockerfileFROM rust:latest WORKDIR /usr/src/projects # 2.数当てゲームをプログラミングする RUN cargo new guessing_game --bin COPY . . RUN cd /usr/src/projects/guessing_game && \ cargo build WORKDIR /usr/src/projects/guessing_game CMD ["cargo", "run"]ビルドし問題なくプログラムが稼働することを確認
$ docker build -t my-rust-app . $ docker run -it --rm --name my-running-app my-rust-app Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/guessing_game` Guess the number! Please input your guess. 0 You guessed: 0 Too small! Please input your guess. 100 You guessed: 100 Too big! Please input your guess. 97 You guessed: 97 You win!おわりに
単にRustの学習環境を作るだけなら、マウントでいいと思います。
- 投稿日:2021-01-19T22:03:43+09:00
【メモ】dockerまとめ
コマンド
起動〜削除まで
- docker pull [imege名]
- image(設計図)を落としてくる
- docker build -t [image名] [ファイルの場所]
- image作成
- -tでイメージ名を指定できる
- docker create -it [imege名]
- コンテナ生成
- -itでターミナルからコンテナ内部の操作できる
- docker start [コンテナ識別子]
- コンテナ起動
- docker stop [コンテナ識別子]
- コンテナ停止
- docker rm [コンテナ識別子]
- コンテナ削除
- ☆docker run [image名]
- pull create start が含まれている
- -dをつけるとバックグラウンド実行
- -p 80:80をつけると80でローカルホストアクセスがきたら80でコンテナ通す
- --name OO で名前つける
- ruby:2.5 などでバージョン指定
- -v $(pwd):/var/www/html
- -v ホスト側のローカルディレクトリ:コンテナの場所
- これでローカルを参照できる
よく使いそう
- docker image ls
- 今までpullしたimageが見える
- docker container ls -a
- コンテナの状況が見える
- -aで停止中のものも見える
- docker exec -it コンテナ名 bash
- コンテナの中に入る
- ctrl+p+qでコンテナ抜ける
dockerファイル
- FROM
- 元にするimage
- LABEL
- 作成したユーザの情報
- RUN
- buildするときに実行される
- CMD
- runするときに実行される
- EXPOSE
- ポートを指定、エクスポート
- WORKDIR
- 作業ディレクトリ指定
- ENV
- 環境変数の指定
- ADD
- ファイル追加
- 投稿日:2021-01-19T21:38:55+09:00
Go(Echo) Go Modules × Dockerで開発環境構築
はじめに
フレームワークに
Echo
ライブラリ管理にgo mod
ホットリロードにfresh
を使用したGoの開発環境をDockerを使って構築したいと思います。
GoのバージョンはGo 1.15
です。この記事ではDockerのインストール方法や細かい解説等はしません。
最終的なディレクトリ構成
. ├── app │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ └── main.go └── docker-compose.yml急いでいる人向け
ここからコードをダウンロードして
docker-compose up
すればできます。Dockerfile作成
まず最初に適当なディレクトリを作成(私の場合go-dockerというディレクトリを作成)
上記のディレクトリ構成を参考にappというディレクトリを作成し、
そしてappディレクトリにDockerfile
という名前でファイルを作成します。DockerfileFROM golang:1.15-alpine WORKDIR /go/src/app ADD ./app /go/src/app RUN apk update && \ apk add --no-cache git && \ go get github.com/labstack/echo/... && \ go get github.com/pilu/fresh EXPOSE 8080 CMD ["fresh"]main.go作成
次はappディレクトリに
main.go
というファイルを作成します。main.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 world") }) e.Logger.Fatal(e.Start(":8080")) }docker-compose.yml作成
次に
docker-compose.yml
という名前でファイルを作成します。
フォルダ構成を参考にファイルを作成する場所に気をつけてください。docker-compose.ymlversion: "3" services: app: build: context: . dockerfile: app/Dockerfile #Dockerfileの場所 volumes: - ./app:/go/src/app ports: - "8080:8080" tty: true #コンテナ永続化build
次にymlファイルがある場所と同じ階層で
docker-compose build
コマンドを実行します。$ docker-compose build Building app Step 1/6 : FROM golang:1.15-alpine ---> b3bc898ad092 Step 2/6 : WORKDIR /go/src/app ---> Running in 55f4bfc0b0e5 Removing intermediate container 55f4bfc0b0e5 ---> bb957624bc5e Step 3/6 : ADD ./app /go/src/app ---> 94a4c0aeb52e Step 4/6 : RUN apk update && apk add --no-cache git && go get github.com/labstack/echo/... && go get github.com/pilu/fresh ---> Running in 2e16203c8eac fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz v3.12.3-65-g746e0b7bc7 [http://dl-cdn.alpinelinux.org/alpine/v3.12/main] v3.12.3-62-gebf75fec7d [http://dl-cdn.alpinelinux.org/alpine/v3.12/community] OK: 12756 distinct packages available fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz (1/5) Installing nghttp2-libs (1.41.0-r0) (2/5) Installing libcurl (7.69.1-r3) (3/5) Installing expat (2.2.9-r1) (4/5) Installing pcre2 (10.35-r0) (5/5) Installing git (2.26.2-r0) Executing busybox-1.31.1-r16.trigger OK: 22 MiB in 20 packages Removing intermediate container 2e16203c8eac ---> dfceb18e2ebd Step 5/6 : EXPOSE 8080 ---> Running in 8353095ba65e Removing intermediate container 8353095ba65e ---> fa4b48a798c4 Step 6/6 : CMD ["fresh"] ---> Running in a8c85cdb33ba Removing intermediate container a8c85cdb33ba ---> e121e6032342 Successfully built e121e6032342 Successfully tagged go-docker_app:latestgo mod init
次に
docker-compose run --rm app go mod init
というコマンドを実行します。
するとappディレクトリにgo.mod
が作成されます。$ docker-compose run --rm app go mod init Creating network "go-docker_default" with the default driver Creating go-docker_app_run ... done go: creating new go.mod: module appdocker-compose up
最後に
ddocker-compose up
というコマンドを実行します。
そしてgo.sum
も作られコンテナが立ち上がります。$ docker-compose up Creating go-docker_app_1 ... done Attaching to go-docker_app_1 app_1 | 9:27:18 runner | InitFolders app_1 | 9:27:18 runner | mkdir ./tmp app_1 | 9:27:18 watcher | Watching . app_1 | 9:27:18 main | Waiting (loop 1)... app_1 | 9:27:18 main | receiving first event / app_1 | 9:27:18 main | sleeping for 600 milliseconds app_1 | 9:27:18 main | flushing events app_1 | 9:27:18 main | Started! (5 Goroutines) app_1 | 9:27:18 main | remove tmp/runner-build-errors.log: no such file or directory app_1 | 9:27:18 build | Building... app_1 | 9:27:34 runner | Running... app_1 | 9:27:34 main | -------------------- app_1 | 9:27:34 main | Waiting (loop 2)... app_1 | 9:27:34 app | app_1 | ____ __ app_1 | / __/___/ / ___ app_1 | / _// __/ _ \/ _ \ app_1 | /___/\__/_//_/\___/ v3.3.10-dev app_1 | High performance, minimalist Go web framework app_1 | https://echo.labstack.com app_1 | ____________________________________O/_______ app_1 | O\ app_1 | 9:27:34 app | ⇨ http server started on [::]:8080この状態になったら下記にアクセスして
Hello world
と表示されていたら成功です。
http://localhost:8080/さいごに
次はMySQLの構築とその接続を時間があればやってみたいと思います。
- 投稿日:2021-01-19T21:12:54+09:00
docker-composeのMySQLのパスワードなどを変えたくなったとき
表題の通り、こんな感じでmysqlのパスワードを設定しているときの設定値の変え方です。
docker-compose.ymlmysql: image: mysql:5.7.32 ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: root_pass MYSQL_DATABASE: db_name MYSQL_USER: user1 MYSQL_PASSWORD: user1_pass volumes: mysql_data:普通にdocker-compose.ymlを変更したのち、次のコマンドを実行すると設定値が反映されます。
docker-compose down --volumes docker-compose up -d普通にdownしてupしただけだと、パスワードが設定済みのMySQLのデータが使われてしまうのでうまくいきません。
そこで前述のコマンドのようにボリュームを削除させる必要があります。
- 投稿日:2021-01-19T20:30:12+09:00
[Node.js]docker-compose upで気楽に開発環境構築
前提
- 筆者はMac環境(試してないがWindowsでも動くと思う)
- dockerコマンドが使える状態
- npmコマンドが使える状態
- nodeコマンドが使える状態
- Dockerの基本的知識(イメージやコンテナの概念)を有する
対象
- 環境構築を手っ取り早くしたい人
- Node.jsをdockerで動かしたい人
環境
bash$ docker -v Docker version 20.10.2, build 2291f61 $ npm -v 6.14.5 $ node -v v13.11.0 $ pwd ~/{project_name}ファイル構成
project{project_name} ├─ node_modules | └─ ... ├─ src | └─ index.js └─ docker-compose.yml └─ Dockerfile └─ package.json └─ package-lock.json
node_modules
の中身は割愛Dockerfileの設定
基本的にはこちらの公式ドキュメント通りにやれば良いが、楽に開発したいたので修正する
{project_name}/DockerfileFROM node:12 # アプリケーションディレクトリを作成する RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Dockerfileが置かれているディレクトリ以下をコピー ADD . /usr/src/app # アプリケーションの依存関係をインストールする COPY package*.json /usr/src/app RUN npm install # 本番用にコードを作成している場合 # RUN npm install --only=production # アプリケーションのソースをバンドルする COPY . /usr/src/app
FROM node:12
:12
の部分はこちらを参考に自身の好きなバージョンで!WORKDIR /usr/src/app
:dockerコンテナの作業ディレクトリを指定
(変更する場合はDockerfileと後に紹介するdocker-compose.ymlに書かれた/usr/src/app
を全て変更する必要あり)docker-compose.ymlの設定
{project_name}/docker-compose.ymlversion: '3' services: app: build: . command: bash -c 'node src/index.js' image: node_test volumes: .:/usr/src/app ports: "8080:8080" tty: true
command: bash -c 'node src/index.js'
:docker-compose up
コマンドでコンテナ起動の際に呼ばれるコマンドimage: node_test
:image名は任意で変更(分かりやすいプロジェクト名とか)volumes: .:/usr/src/app
:ローカルのファイルとdockerコンテナ内のファイルを同期させるports: "8080:8080"
:ローカルとコンテナ両方とも8080番ポートを使用tty: true
:docker-compose up
でコンテナが終了しないようにする(index.jsの設定)
基本的に自身の環境による
以下はこちらの雛形'use strict'; const express = require('express'); // Constants const PORT = 8080; const HOST = '0.0.0.0'; // App const app = express(); app.get('/', (req, res) => { res.send('Hello World'); }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`);起動
bash$ docker-compose up
Have fun !!
※ イメージができてない場合、コンテナ起動までに時間がかかる
参考文献
- 投稿日:2021-01-19T20:07:31+09:00
Go + MySQL + nginxの開発環境をDocker(docker-compose)で作る
やりたいこと
- Go、MySQL、nginxの開発環境をDocker(docker-compose)で作る
- Goの外部パッケージはGo Modulesで管理
- Goのプロジェクトを実践的なものにする
- DBのテーブル管理はマイグレーションを使う
- testはテスト用のDBを使う
こんな人におすすめ
- GoとMySQLでAPIサーバーの開発がしたい
- 環境構築はDockerで手っ取り早く済ませたい
- 拡張しやすいGoのプロジェクトが欲しい
使用するフレームワーク、バージョン
バージョン Go 1.15 MySQL 5.7 nginx 1.19 ディレクトリ構成
├── docker-compose.yml ├── Dockerfile ├── app │ ├── cmd │ │ ├── migrate │ │ │ └── main.go │ │ └── server │ │ └── main.go │ ├── db │ │ └── migrations │ │ ├── 1_create_users.down.sql │ │ └── 1_create_users.up.sql │ ├── go.mod │ ├── go.sum │ └── pkg │ ├── connecter │ │ └── connecter.go │ ├── controller │ │ ├── router.go │ │ └── users.go │ └── model │ ├── main_test.go │ ├── user.go │ └── user_test.go ├── mysql │ ├── Dockerfile │ ├── docker-entrypoint-initdb.d │ │ └── init.sql │ └── my.cnf └── nginx ├── Dockerfile └── default.confルートディレクトリにあるDockerfileがGoのコンテナ用です。
使い方
GitHubレポジトリはこちらにあります。
https://github.com/fuhiz/docker-go-sampleまずはdocker-compose.ymlがあるディレクトリでコンテナを立ち上げます。
$ docker-compose up -d --buildGoのコンテナに入ります。
$ docker-compose exec web bashGoのコンテナの/appでマイグレーションを実行します。
実行されるSQLはapp/db/migrationsのファイルです。
usersとマイグレーション管理のためのschema_migrationsが作られます。$ go run cmd/migrate/main.go -exec upusersには名前(name)、年齢(age)、日時カラムを用意しました。
/db/migrations/1_create_users.up.sqlCREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `deleted_at` datetime, PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8mb4;サーバーを起動します。
$ go run cmd/server/main.goこれでlocalhost:8082でGoのAPIにつながるようになります。
APIを使ってみる
DBにはusersテーブルがあって、ユーザーのCRUD機能が使えるようになっているので、curlで確認します。
- ユーザー作成
$ curl localhost:8082/api/v1/users -X POST -H "Content-Type: application/json" -d '{"name": "test", "age":30}'
- ユーザー一覧
$ curl localhost:8082/api/v1/users {"users":[{"ID":1,"CreatedAt":"2021-01-09T11:09:31+09:00","UpdatedAt":"2021-01-09T11:09:31+09:00","DeletedAt":null,"name":"test","age":30}]}%先ほど作ったユーザーが取得できます。
- ユーザー更新
$ curl localhost:8082/api/v1/users/1 -X PATCH -H "Content-Type: application/json" -d '{"name": "update", "age":31}'
- ユーザー削除
$ curl localhost:8082/api/v1/users/1 -X DELETEdocker-compose.yml
ここから環境構築の細かいところを見ていきます。
docker-compose.ymlの基本的な書き方には触れないので、参考にされる方はこちらを。
https://qiita.com/zembutsu/items/9e9d80e05e36e882caaaそれぞれのserviceについてはこのようになっております。
db
- MySQLコンテナ
- ユーザー名やパスワードなどを環境変数で定義。
- docker-entrypoint-initdb.dをマウントして、コンテナ起動時にdocker-entrypoint-initdb.d/init.sqlが実行されるようにします。init.sqlでgo_sampleとgo_sample_testというデータベースを作ります。 /docker-entrypoint-initdb.dはMySQLのDockerイメージに備わっているディレクトリで初期データを作ることができます。
- ホスト側のポートが3310なのはローカルで動かすMySQLと被らないようにするためです。
- Sequel Proで接続するときはこうなります。
※パスワードはlocalpass。データベースは空でも構わないです。web
- Goコンテナ
- 起動後すぐにコンテナが閉じてしまわないようにtty: trueでコンテナを永続化します。 (サーバー起動をDockerfileに書かず、コンテナの中で手動で実行するためです)
- Goプロジェクト内で使う環境変数を定義。Goのコードで
os.Getenv("DB_PASSWORD")
とすればこの値が読み込めます。DB_HOSTのdbはMySQLコンテナのサービス名です。GORMでDB接続するときにこのサービス名で接続できます。- Goプロジェクトがある./app(ホスト)を/app(コンテナ)にマウントします。コンテナ内の/appはDockerfileのWORKDIRで指定したときに作成されます。
proxy
- nginxはリバースプロキシによってURLを転送します。ここでは
http://localhost
がGoのAPIになるように設定する目的で使います。- ホスト側のportは8082を指定しました。
docker-compose.ymlversion: "3" services: db: build: ./mysql environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: localuser MYSQL_PASSWORD: localpass TZ: Asia/Tokyo volumes: - ./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d ports: - "3310:3306" web: build: . tty: true environment: APP_MODE: local DB_PASSWORD: localpass volumes: - "./go:/app" depends_on: - db proxy: build: ./nginx ports: - 8082:80 depends_on: - webMySQLコンテナ
MySQLのDockerfileはこれらの一般的な設定です。
タイムゾーンをAsia/Tokyoにする。
設定ファイルのmy.cnfをコピーする。
起動時に実行するinit.sqlをコピーする。mysql/DockerfileFROM mysql:5.7 ENV TZ Asia/Tokyo RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && chown -R mysql:root /var/lib/mysql/ COPY my.cnf /etc/mysql/conf.d/my.cnf COPY docker-entrypoint-initdb.d/init.sql /docker-entrypoint-initdb.d/ CMD ["mysqld"] EXPOSE 3306init.sqlではgo_sampleとテスト用のgo_sample_testを作ってユーザーの権限設定をします。
mysql/docker-entrypoint-initdb.d/init.sqlCREATE DATABASE IF NOT EXISTS `go_sample` COLLATE 'utf8mb4_general_ci' ; CREATE DATABASE IF NOT EXISTS `go_sample_test` COLLATE 'utf8mb4_general_ci' ; GRANT ALL ON `go_sample`.* TO 'localuser'@'%' ; GRANT ALL ON `go_sample_test`.* TO 'localuser'@'%' ; FLUSH PRIVILEGES ;Goコンテナ
内容はコメントの通りで、外部パッケージをダウンロードするためにgo.modとgo.sumを事前にコピーしています。
DockerfileFROM golang:1.15 ## 作業ディレクトリ WORKDIR /app # モジュール管理のファイルをコピー COPY go/go.mod . COPY go/go.sum . # 外部パッケージのダウンロード RUN go mod download EXPOSE 9000nginxコンテナ
nginx.confで読み込むdefault.confをコピーします。
nginx/DockerfileFROM nginx:1.19-alpine COPY ./default.conf /etc/nginx/conf.d/default.conf EXPOSE 80nginx/default.confserver { listen 80; server_name localhost; location / { proxy_pass http://web:9000; } }nginxの設定ファイルである/etc/nginx/nginx.confで/etc/nginx/conf.d/配下の*.confを読み込むようになっているので、読み込まれる部分だけを作っています。
nginxの設定はこちらが参考になります。
https://qiita.com/morrr/items/7c97f0d2e46f7a8ec967大事なのは
proxy_pass http://web:9000;
の部分で、ここでhttp://localhost
をhttp://web:9000
に置き換えています。
webはdocker-compose.ymlで定義したGoコンテナのサービス名です。
docker-composeはサービス間でネットワーク通信できるので、このような指定ができます。
Goプロジェクトはポートを9000でサーバーを立ち上げているのでポートはそれに合わせます。また、docker-compose.ymlのnginxコンテナでポートを8082:80としているので、ホストからは
http://localhost:8082
でアクセスします。ややこしいですが、とどのつまりは
http://localhost:8082
でGoのAPIが叩けることになります。Goのプロジェクト概要
Goのコードはなるべく実践的に使えるものを意識して作りました。
ディレクトリ構造はこちらを参考にしています。
https://qiita.com/sueken/items/87093e5941bfbc09bea8cmd
アプリケーションのエントリーポイント。
サーバー起動とマイグレーション機能を配置。db
マイグレーションで実行したいsqlファイルを配置。pkg
アプリケーションの挙動に関わる部分。
モデル(model)、コントローラー(controller)、接続(connecter)を作成。マイグレーション
マイグレーション周りはこちらを参考にさせていただきました。
https://qiita.com/tanden/items/7b4fb1686a61dd5f580dgolang-migrateを使用して、db/migrationsにあるsqlファイルでDBを管理します。
ファイル名のルールは{version}を昇順にすれば、番号でもタイムスタンプでも問題ありません。
https://github.com/golang-migrate/migrate/blob/master/MIGRATIONS.md{version}_{title}.up.{extension} {version}_{title}.down.{extension}ここではusersテーブルを作成する
1_create_users.up.sql
とテーブル削除用の1_create_users.down.sql
を作成しました。マイグレーション管理のファイルは
cmd/migrate/main.go
にあります。内容は参考サイトのほぼコピペになります。このファイルを実行すれば追加した分の*.up.sqlだけが走ります。
$ go run cmd/migrate/main.go -exec up戻したいときはオプションをdownにすれば、全ての*.down.sqlが実行されます。
$ go run cmd/migrate/main.go -exec downtest用のデータベースに接続したいときはAPP_MODE=testで環境変数つきで実行します。
$ APP_MODE=test go run cmd/migrate/main.go -exec upcmd/migrate/main.goのinit()でAPP_MODEがtestなら、データベースはDB_NAME_TESTを使うようにしてます。
cmd/migrate/main.gofunc init() { // database name decide by APP_MODE dbName := os.Getenv("DB_NAME") if os.Getenv("APP_MODE") == "test"{ dbName = os.Getenv("DB_NAME_TEST") } Database = fmt.Sprintf("mysql://%s:%s@tcp(%s:%s)/%s", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), dbName) }GoのAPIの処理の流れ
エントリーポイントとなるファイルはcmd/server/main.go。
ginを使って、ポート9000でサ-バーを立ち上げています。cmd/server/main.gopackage main import ( "net/http" "github.com/gin-gonic/gin" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/controller" ) func main() { // gormのDB接続 connecter.Setup() router := gin.Default() // apiの疎通確認用 router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Response OK") }) // routing r := router.Group("/api/v1") controller.Setup(r) router.Run(":9000") }細かい処理は/pkgのcontrollerなどを使っていきます。
gormのDB接続はpkg/connecter/connecter.goで行います。
変数dbに*gorm.DBを格納して、DB()で呼び出せる形になっています。接続の仕方は公式を見れば大体把握できます。
https://gorm.io/docs/connecting_to_the_database.htmlデータベースの各パラメータはdocker-compose.ymlで定めた環境変数から取得しています。
ここでもAPP_MODEがtestならDB_NAME_TESTを使います。pkg/connecter/connecter.gopackage connecter import ( "fmt" "os" "gorm.io/driver/mysql" "gorm.io/gorm" ) var db *gorm.DB func Setup() { // APP_MODEからデータベース名を決める dbName := os.Getenv("DB_NAME") if os.Getenv("APP_MODE") == "test"{ dbName = os.Getenv("DB_NAME_TEST") } // DB接続 (https://gorm.io/docs/connecting_to_the_database.html) dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=%s", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), dbName, os.Getenv("DB_LOC")) gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } db = gormDB } func DB() *gorm.DB { return db }ルーティングはpkg/controllers/router.goに書きます。
それぞれpkg/controllers/users.goのfuncを呼びます。pkg/controllers/router.gopackage controller import ( "github.com/gin-gonic/gin" ) func Setup(r *gin.RouterGroup) { users := r.Group("/users") { u := UserController{} users.GET("", u.Index) users.GET("/:id", u.GetUser) users.POST("", u.CreateUser) users.PATCH("/:id", u.UpdateUser) users.DELETE("/:id", u.DeleteUser) } }pkg/controllers/users.goでは処理に応じて/pkg/model/user.goのfuncを呼びます。
pkg/controllers/users.gopackage controller import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/model" ) type UserController struct{} type UserParam struct { Name string `json:"name" binding:"required,min=1,max=50"` Age int `json:"age" binding:"required,number"` } // ユーザー取得 func (self *UserController) GetUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー一覧 func (self *UserController) Index(c *gin.Context) { users, err := model.GetUsers(connecter.DB()) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user search failed"}) return } c.JSON(http.StatusOK, gin.H{"users": users}) } // ユーザー作成 func (self *UserController) CreateUser(c *gin.Context) { var param UserParam if err := c.BindJSON(¶m); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } newUser := model.NewUser(param.Name, param.Age) user, err := model.CreateUser(connecter.DB(), newUser) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user create failed"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー更新 func (self *UserController) UpdateUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } var param UserParam if err := c.BindJSON(¶m); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updateParam := map[string]interface{}{ "name": param.Name, "age": param.Age, } _, err = user.Update(connecter.DB(), updateParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user update failed"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー削除 func (self *UserController) DeleteUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } _, err = user.Delete(connecter.DB()) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user delete failed"}) return } c.JSON(http.StatusOK, gin.H{"deleted": true}) }/pkg/model/user.gopackage model import ( "gorm.io/gorm" ) type User struct { gorm.Model Name string `json:"name"` Age int `json:"age"` } func NewUser(name string, age int) *User { return &User{ Name: name, Age: age} } func CreateUser(db *gorm.DB, user *User) (*User, error) { result := db.Create(&user) return user, result.Error } func GetUsers(db *gorm.DB) ([]*User, error) { users := []*User{} result := db.Find(&users) return users, result.Error } func GetUserById(db *gorm.DB, ID int) (*User, error) { user := User{} result := db.First(&user, ID) return &user, result.Error } func (user *User) Update(db *gorm.DB, param map[string]interface{}) (*User, error) { result := db.Model(&user).Updates(param) return user, result.Error } func (user *User) Delete(db *gorm.DB) (*User, error) { result := db.Delete(&user) return user, result.Error }テスト
テストはGoのコンテナ内でAPP_MODE=testをつけて実行します。
以下手順。マイグレーション(up)でgo_sample_testにテーブルを作成。
$ APP_MODE=test go run cmd/migrate/main.go -exec up/pkgをテスト。
$ APP_MODE=test go test -v ./pkg/...次のテストのためにgo_sample_testを戻す。
$ APP_MODE=test go run cmd/migrate/main.go -exec downテストファイルは/pkg/modelにmain_test.goとuser_test.goがあります。
TestMainが最初に実行されるので、そこでDB接続しときます。/pkg/model/main_test.gopackage model_test import ( "testing" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" ) func TestMain(m *testing.M) { connecter.Setup() m.Run() }ユーザー作成のテスト。
/pkg/model/user_test.gopackage model_test import ( "testing" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/model" ) func TestCreateUser(t *testing.T) { newUser := model.NewUser("test_user", 30) user, _ := model.CreateUser(connecter.DB(), newUser) if user.Name != "test_user" { t.Fatal("model.CreateUser Failed") } }まとめ
ローカル環境としてはそれなりに使える環境が整えられたと思います。
自動テストやデプロイにも対応できるかは今後検証していきたいです。Goはまだまだベストプレクティスが確立されていないようでテスト環境の切り分けは苦労しました。
改めてLaravelやRailsのような全部入りのフレームワークの偉大さも感じました。長めの記事でしたが参考にしてもらえたらありがたいです!
- 投稿日:2021-01-19T19:51:54+09:00
Rails6[APIモード]+ MySQL5.7 を Docker で環境構築
はじめに
自分用です
Rails6 APIモード + MySQL5.7 を Docker(docker compose) で環境構築Dockerそのものの導入は省略
はじめに環境構築に必要なファイルを作成
- 以下のファイルを作成する
- アプリ用のトップレベルディレクトリ
- Dockerfile
- docker-compose.yml
- Gemfile
- Gemfile.lock
アプリ用のトップレベルディレクトリ作成&移動
$ cd $ mkdir sample_app $ cd sample_app
Dockerfile, docker-compose.yml, Gemfile, Gemfile.lock作成
sample_app$ touch {Dockerfile,docker-compose.yml,Gemfile,Gemfile.lock} sample_app$ ls Dockerfile docker-compose.yml Gemfile Gemfile.lock
ファイルの中身書いていく
sample_app/Dockerfileファイル
DockerfileFROM ruby:2.6.5 # 必要なパッケージのインストール(Rails6からWebpackerがいるので、yarnをインストールする) RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y build-essential libpq-dev nodejs yarn # 作業ディレクトリの作成 RUN mkdir /myapp WORKDIR /myapp # ホスト側(ローカル)(左側)のGemfileを、コンテナ側(右側)のGemfileへ追加 ADD ./Gemfile /myapp/Gemfile ADD ./Gemfile.lock /myapp/Gemfile.lock # Gemfileのbundle install RUN bundle install ADD . /myapp
sample_app/docker-compose.yml
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" web: build: . command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3001 -b '0.0.0.0'" tty: true stdin_open: true depends_on: - db ports: - "3001:3001" volumes: - .:/myapp
sample_app/Gemfile
Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.3'
Gemfile.lockは空のまま
docker-compose run コマンドで Rails アプリを作成
- APIモードなので
--api
オプション付与- バージョン6以降なので、
--webpacker
オプション付与$ docker-compose run web rails new . --force --database=mysql --skip-bundle --api --webpacker
database.yml ファイルを修正
- sample_app/config/database.yml ファイルに、コンテナに作成されたDB情報を記述する
database.ymldefault: &dafault adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password # docker-compose.ymlの MYSQL_ROOT_PASSWORD host: db # docker-compose.ymlの service名
Dockerコンテナを起動する
- コンテナの実行
$ docker-compose build
- コンテナを起動
-d
オプション付与でバックグラウンド実行をする。これを実行すると、コンテナを起動したままプロンプト画面へ戻る$ docker-compose up -d
DBを作成する
- まだコンテナを起動していない場合はしておく
$ docker-compose up -d
- コンテナID確認
$ docker ps -a
- 確認したIDより、コンテナに入る
$ docker exec -it <コンテナのID> /bin/bash
- DB作成
$ rails db:create $ rails db:migrate
- コンテナから出る
$ exit
- コンテナに入らず、ローカルから実行する場合。コンテナ起動後に、
docker-compose run
コマンドを実行する$ docker-compose run web rails db:create $ docker-compose run web rails db:migrate
構築は以上。
localhost:3001
で開くようになった。
その他
サーバーを止める場合
- Ctrl + C で止めないこと。コンテナが残って次回起動時にエラーが出る
- もしやってしまった場合、tmp/pids/server.pid を削除して、再びdocker-compose upで再起動する
$ docker-compose down
Dockerfileやdocker-compose.ymlの変更を反映、railsサーバー再起動
$ docker-compose up --build
bundle install などのコマンドを実行したい場合
# docker-compose run { サービス名 } { 任意のコマンド } $ docker-compose run web bundle install
ローカルからMySQLコンテナに接続
- コンテナ起動してない場合は起動
$ docker-compose up -d
- mysqlのidを確認
$ docker ps
- MySQLコンテナにログイン
$ docker exec -it <MySQLのコンテナのID> /bin/bash
$ mysql -u root -p -h 0.0.0.0 -P 3306 --protocol=tcp mysql> // 脱出 mysql> quit
以上。
参考にさせて頂いた記事
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
【Rails】Rails 6.0 x Docker x MySQLで環境構築
- 投稿日:2021-01-19T17:10:20+09:00
Docker in LXD
Docker in LXD
Docker をあれこれ使ってたら、Docker Engine をたくさん立ち上げる羽目になったりしませんか? 少なくとも私はそうです。
方法はいろいろとあるのですが、Docker in LXD が昔と違って楽に構築できるので最近は Docker Engine をインストールするときは LXD コンテナーの上に構築しています。
LXD のセットアップ
Ubuntu 20.04 LTS はインストール時に Snap で LXD がインストール済みです。でも、LXD を使えるようにするために
lxd init
で初期化する必要があります。$ lxd init Would you like to use LXD clustering? (yes/no) [default=no]: ...なんか色々聞かれます。基本的にデフォルトでいいのですが、ストレージバックエンドは必ず
btrfs
を選びます。(デフォルトがzfs
なことが多いようで、明示的にbtrfs
にしてください)... Name of the storage backend to use (ceph, btrfs, dir, lvm) [default=btrfs]: btrfs ...動作確認のために、LXD コンテナーを作成し起動して、そのまま終了します。
$ lxc launch ubuntu:20.04 ubuntu Creating ubuntu Starting ubuntu $ lxc exec ubuntu bash root@ubuntu:~# exit動作確認が終わったのでコンテナーを削除します。
$ lxc rm -f ubuntuDocker in LXD
LXD の素のコンテナーでは Docker Engine はインストールできるかもしれませんが、Docker コンテナーは起動しません。
LXD のドキュメントの LXD コンテナー内で docker を実行できますか?によると、
security.nesting
をtrue
にすればよいようです。$ lxc launch -c security.nesting=true ubuntu:20.04 docker Creating docker Starting docker念のため、設定されているかを調べます。
$ lxc config show docker config: ... security.nesting: "true" ...問題ないので、Docker Engine をインストールします。
$ lxc exec docker bash root@docker:~# curl https://get.docker.com | shインストールが終わったので Docker コンテナーを起動します。
root@docker:~# docker run --rm hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/動きました!
ストレージドライバーの選定
LXD や Docker Engine はストレージドライバーを選べます。ですが、起動する組み合わせとなると少ないようです。今回の手順で構築した場合は、LXD はも Docker Engine も
btrfs
で動作しています。交換可能なストレージ・ドライバ構造 にある表によると、よく使われる Docker Engine のストレージドライバーの
overlay2
はホスト側のストレージがext4
やxfs
でなければなりません。動きそうな組み合わせは以下の通りです。
LXD のストレージドライバー Docker Engine のストレージドライバー btrfs btrfs zfs zfs lvm devicemapper 手元でうまくいったのは
btrfs
のみです。zfs
は LXD コンテナー上でzfsutils-linux
をインストールするのに失敗しました。lvm
は面倒なので試していません。Docker Engine の
btrfs
ストレージドライバーはそれほど性能がよくなさそうなのですが、動くだけでもありがたいです。なお、Docker Engine のストレージドライバーには
vfs
というのもありまして、こいつなら LXD のストレージドライバーにかかわらず動くっぽいのですが、開発用で性能最悪らしいです。
security.privileged=true
ググると LXD コンテナーの設定で
security.privileged
をtrue
にしているのも見かけます。起動する Docker コンテナーによっては必要になるっぽいのですが、これを有効にするとコンテナーからホスト側へいろいろできてしまうので不要ならやらない方がよさそうです。
security.nesting=true
の安全性LXD の公式ドキュメントで
security.nesting=true
が安全かどうか (コンテナーからホスト側にアクセスできないか) についての記述が見つかりませんでした。ググって調べてみても情報がなくて分かりませんでした。このあたり詳しい人がいたら教えてほしいです。
- 投稿日:2021-01-19T16:16:27+09:00
Docker 簡単インストール手順(CentOS)
本記事の内容
- Linux(CentOS)にDockerを簡単にインストールする。
curl | sh
でインストールする際の注意点を理解する。※本手順はインストールするDockerのバージョンや依存パッケージを指定できないため、本番環境での使用は推奨されない。
前提条件
- CentOS 7 または 8
- Dockerがインストールされていない。
AWS Lightsail の CentOS バージョン 7.9.2009 で動作を確認。
※インストールスクリプトはDebian、Fedora、Raspbian、RHEL、Ubuntuにも対応しているが、コマンド
yum
、curl
、systemctl
の部分は適宜読み替える必要がある。
※RHELはコマンドが同一のため、本手順がそのまま使える想定。インストール手順
1. 未インストール確認
$ yum list installed | grep docker何も表示されなければ、Dockerがインストールされていないので作業を進める。
2. 管理者(root)への切替
$ sudo su -3. インストール
公式サイトより、
curl
でインストールスクリプトを取得し、sh
で実行する。# curl -fsSL https://get.docker.com | shスクリプトはLinuxの種類を自動判別し、必要なコマンドを実行する。
CentOSの場合の主な内容は以下の通り。
- 「yum-utils」のインストール。(2. の実行に必要)
- 「yum-config-manager」で、Dockerの取得先リポジトリ
https://download.docker.com/linux/centos/docker-ce.repo
を追加。- 「docker-ce」と依存パッケージをインストール。
スクリプトの内容はブラウザからも確認できる。
https://get.docker.comこのようなインストール方法には注意点もある。
提供スクリプトに悪意がある場合はもちろんだが、ブラウザから確認した際には正しいスクリプトを返し、curlなどコマンドから要求した際には不正なスクリプトを返すことも、技術的には可能。
curl -fsSL https://get.docker.com
のように、| sh
を除けばスクリプトを実行せずに内容を確認できるので、心配な場合は確認する。また、スクリプトを取得する際に一部が欠落し、意図しないコマンドが実行される可能性もある。
get.docker.comは、インストール処理を関数「do_install」として定義し、最終行で実行する対策がされている。4. インストールされたパッケージの確認
$ yum list installed | grep docker containerd.io.x86_64 1.4.3-3.1.el7 @docker-ce-stable docker-ce.x86_64 3:20.10.2-3.el7 @docker-ce-stable docker-ce-cli.x86_64 1:20.10.2-3.el7 @docker-ce-stable docker-ce-rootless-extras.x86_64 20.10.2-3.el7 @docker-ce-stable5. 起動・OS起動時の自動起動設定
# systemctl start docker && systemctl enable docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.6. バージョン確認
# docker version Client: Docker Engine - Community Version: 20.10.2 API version: 1.41 Go version: go1.13.15 Git commit: 2291f61 Built: Mon Dec 28 16:17:48 2020 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.2 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 8891c58 Built: Mon Dec 28 16:16:13 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.4.3 GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b runc: Version: 1.0.0-rc92 GitCommit: ff819c7e9184c13b7c2607fe6c30ae19403a7aff docker-init: Version: 0.19.0 GitCommit: de40ad07. 動作確認(hello-world)
# docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:31b9c7d48790f0d8c50ab433d9c3b7e17666d6993084c002c2ff1ca09b96391d Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/参考
公式サイト
Install Docker Engine on CentOS | Docker Documentation
- 投稿日:2021-01-19T15:01:05+09:00
M1 MAC(Apple Silicon) でmysqlイメージを動かす
docker-compose.yml
に以下の様にplatformを設定するservices: db: platform: linux/x86_64 image: mysql:5.7 ...なんでこれで動くのか不思議だったが、
https://www.publickey1.jp/blog/20/apple_m1docker_desktopcpux86docker.html
によると、
「Docker Desktop for Mac」はマルチCPUアーキテクチャ対応しており、上記の指定だと、
linux/x86_64用のイメージをエミュレーションして動作させることになるっぽい。
エミュレーションするため遅くはなるかもしれない
- 投稿日:2021-01-19T13:47:06+09:00
既存Rails6アプリのDocker環境への移行
はじめに
ローカル環境でrailsを使用し開発を進めていましたが、今後のデプロイを考慮し、環境をDockerによるコンテナで管理することとしました。
初めてでかなり時間がかかってしまったため、備忘録として残します。
同様の環境構築が必要な方の参考になれば幸いです。環境構築にあたっての目標
- ローカルの環境をコンテナ化し、本番環境構築時に容易にしたい。
- Dockerのイメージは、効率化を考慮し、できるだけ軽量化したい。
- bundleやyarnのモジュールを永続化することにより、起動にかかる時間を減らしておきたい。
- DBはsqliteを使用していたので、ついでにMysqlにしたい。
構築した環境
コンテナの構成
- db
- web
- webpacker←webpack_dev_server実行用
バージョン
- ruby 2.7.2
- Rails 6.0.3
- Mysql 8.0.22
一覧
今回変更した箇所のみ記載しています。
ルートディレクトリはrailsアプリのルートディレクトリとしています。. ├── config │ ├── database.yml #更新 │ └── webpacker.yml #更新 ├── docker │ └── rails │ └── Dockerfile #新規作成 ├── docker-compose.yml #新規作成 ├── .env #新規作成 └── Gemfile #更新Dockerfile
Dockerfile#軽量化のため、alpineを使用。 FROM ruby:2.7.2-alpine3.12 ENV TZ="Asia/Tokyo" \ LANG="C.UTF-8" \ APP_ROOT="/app" \ ENTRYKIT_VERSION="0.4.0" WORKDIR $APP_ROOT #ENTRY KITの導入 RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \ && mv entrykit /bin/entrykit \ && chmod +x /bin/entrykit \ && entrykit --symlink RUN apk update \ && apk add --no-cache \ alpine-sdk \ bash \ build-base \ mysql-client \ mysql-dev \ nodejs \ tzdata \ yarn COPY . $APP_ROOT ENTRYPOINT [ \ "prehook", "bundle install -j4 --quiet", "--", \ "prehook", "yarn install --check-files --ignore-optional", "--"]イメージの軽量化
今後の作業の効率化のため、イメージの軽量化するために、alpineを使用しています。
約1.6GB→約400MBまで容量が減りました。マルチステージビルドを使用し、nodejsのインストールを別にすることで、さらにイメージの軽量化ができそうです。
ある程度軽量化できたので、今回はここまでとしました。ENTRY KITの導入
ENTRY KITを使用し、コンテナ起動時に
yarn install
、bundle install
を実行するようにしています。
調べた記事では、Dockerfile内で実行しているものが多かったですが、以下の問題があるため今回は見送りました。
- gem等の追加が必要となった時は、コンテナのbuildからやり直す必要がある。
- コンテナをdownすると、installしたgem等が保持されない。
そのためコンテナ起動時の実行かつ、モジュールを永続化することで、上記の問題を解決する構成としました。
docker-compose.yml
docker-compose.ymlversion: '3' services: db: image: mysql:8.0.22 command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci ports: - '3306:3306' volumes: - db_data:/var/lib/mysql environment: MYSQL_DATABASE: ${DATABASE} MYSQL_ROOT_PASSWORD: ${ROOTPASS} MYSQL_USER: ${USERNAME} MYSQL_PASSWORD: ${USERPASS} web: &app_base build: context: . dockerfile: ./docker/rails/Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b 0.0.0.0" depends_on: - db ports: - "3000:3000" volumes: - .:/app - gem_modules:/vendor/bundle - node_modules:/node_modules tty: true #binding.pry実行用 stdin_open: true #binding.pry実行用 environment: WEBPACKER_DEV_SERVER_HOST: webpacker #webpack_dev_server実行用のコンテナを指定 BUNDLE_APP_CONFIG: ./.bundle NODE_ENV: development RAILS_ENV: development webpacker: #webpack_dev_server実行用のコンテナ <<: *app_base command: bash -c "bundle exec bin/webpack-dev-server" depends_on: - web ports: - '3035:3035' tty: false #binding.pry不要なのでfalseへ変更 stdin_open: false #binding.pry不要なのでfalseへ変更 environment: BUNDLE_APP_CONFIG: ./.bundle WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 NODE_ENV: development RAILS_ENV: development volumes: db_data: gem_modules: node_modules:
- DBのユーザ情報は別ファイルとしておきたかったので、変数化し
.env
に記載しています。- コンテナをdownさせても、DB、モジュールのデータが失われないよう、永続化しています。
database.yml
database.ymldefault: &default - adapter: sqlite3 + adapter: mysql2 + username: app + password: password + host: db #サービス名を指定 development: <<: *default - database: db/development.sqlite3 + database: mysql_developmentwebpacker.yml
webpacker.ymldefault: &default dev_server: - host: localhost + host: webpack #サービス名を指定Gemfile
Gemfile-gem 'sqlite3', '~> 1.4' +gem 'mysql2', '0.5.3'.env
.envDATABASE=mysql_development USERNAME=app USERPASS=password ROOTPASS=password疑問点・解消できなかったこと
コンテナ初回起動時エラー(stack Error: getaddrinfo EAI_AGAIN nodejs.org)
コンテナ初回起動時の
yarn install
実行中に、エラーが発生することがあります。
調べた結果、原因はDNSで名前解決できていないことだそう。解決方法と思われるホスト側のDNS変更や、
docker-compose.yml
でのDNSの指定等試しましたが、解決しませんでした。
2回目起動時はエラーが発生せず、コンテナの再起動を解決方法としている記事もあったため、このままとしておきます。error /app/node_modules/node-sass: Command failed. web_1 | Exit code: 1 web_1 | Command: node scripts/build.js web_1 | Arguments: web_1 | Directory: /app/node_modules/node-sass web_1 | Output: web_1 | Building: /usr/bin/node /app/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library= web_1 | gyp info it worked if it ends with ok web_1 | gyp verb cli [ web_1 | gyp verb cli '/usr/bin/node', web_1 | gyp verb cli '/app/node_modules/node-gyp/bin/node-gyp.js', web_1 | gyp verb cli 'rebuild', web_1 | gyp verb cli '--verbose', web_1 | gyp verb cli '--libsass_ext=', web_1 | gyp verb cli '--libsass_cflags=', web_1 | gyp verb cli '--libsass_ldflags=', web_1 | gyp verb cli '--libsass_library=' web_1 | gyp verb cli ] web_1 | gyp info using node-gyp@3.8.0 web_1 | gyp info using node@12.20.1 | linux | x64 web_1 | gyp verb command rebuild [] web_1 | gyp verb command clean [] web_1 | gyp verb clean removing "build" directory web_1 | gyp verb command configure [] web_1 | gyp verb check python checking for Python executable "python2" in the PATH web_1 | gyp verb `which` succeeded python2 /usr/bin/python2 web_1 | gyp verb check python version `/usr/bin/python2 -c "import sys; print "2.7.18 web_1 | gyp verb check python version .%s.%s" % sys.version_info[:3];"` returned: %j web_1 | gyp verb get node dir no --target version specified, falling back to host node version: 12.20.1 web_1 | gyp verb command install [ '12.20.1' ] web_1 | gyp verb install input version string "12.20.1" web_1 | gyp verb install installing version: 12.20.1 web_1 | gyp verb install --ensure was passed, so won't reinstall if already installed web_1 | gyp verb install version not already installed, continuing with install 12.20.1 web_1 | gyp verb ensuring nodedir is created /root/.node-gyp/12.20.1 web_1 | gyp verb created nodedir /root/.node-gyp web_1 | gyp http GET https://nodejs.org/download/release/v12.20.1/node-v12.20.1-headers.tar.gz web_1 | gyp WARN install got an error, rolling back install web_1 | gyp verb command remove [ '12.20.1' ] web_1 | gyp verb remove using node-gyp dir: /root/.node-gyp web_1 | gyp verb remove removing target version: 12.20.1 web_1 | gyp verb remove removing development files for version: 12.20.1 web_1 | gyp ERR! configure error web_1 | gyp ERR! stack Error: getaddrinfo EAI_AGAIN nodejs.org web_1 | gyp ERR! stack at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26) web_1 | gyp ERR! System Linux 4.19.121-linuxkit web_1 | gyp ERR! command "/usr/bin/node" "/app/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library=" web_1 | gyp ERR! cwd /app/node_modules/node-sass web_1 | gyp ERR! node -v v12.20.1 web_1 | gyp ERR! node-gyp -v v3.8.0 web_1 | gyp ERR! not okマルチステージビルドでのイメージの軽量化
時間短縮等、更なる効率化が必要となった時に試してみようと思います。
最後に
新規のRailsアプリをDockerで環境構築する記事はあったのですが、既存のRailsアプリをコンテナ化する記事はあまりなかったので苦労しました。
開発環境として一旦構築したので、テスト環境や本番環境の分け方・構築も検討して取り組んでいきます。他にも以下の内容も取り組んでいきたいので、実施できれば記事にしようかと思います。
- webサーバとしてnginxの導入
- CI/CDの導入
- AWS上での環境構築
参考にさせて頂いた記事
・開発しやすいRails on Docker環境の作り方 - Qiita
・Rails newからproductionモードで動くようになるまで - Qiita
・Rails 6.0 × MySQL8でDocker環境構築(Alpineベース) - Qiita
・Docker Composeのvolumesを使ってもっと効率的に - Qiita
・【Dockerfile全解説】Rails本番環境のための一番シンプルなDockerイメージを作る - Qiita
・【Docker】Rails開発で知っておきたい!gemの永続化による作業効率アップの話 | Enjoy IT Life
・docker-composeでの環境構築で留意しておきたいところ - Qiita
・Rails + webpacker on Dockerの環境をdocker-composeで構築する - RoadMovie