20201201のdockerに関する記事は25件です。

【MySQL 8.0.22】 レプリケーション on Docker

Dockerを使ってMySQLのレプリケーション環境を構築していきたいと思います。

Dockerfileやdocker-composeを使った構築方法に関しては、結構たくさん記事があるので、
趣向を変えてコマンドだけで構築していきたいなと思います。

  • 環境
    • macOS Catalina ver: 10.15.17
    • Docker Engine ver: 19.03.13

完成図

スクリーンショット 2020-11-29 21.19.07のコピー.png

ネットワークの作成

「192.168.220.0/24」の範囲で「mysql_net」という名前のネットワークを構築します。
コマンドは以下です。

docker network create -d bridge --subnet=192.168.220.0/24 mysql_net

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

docker network ls -f "NAME=mysql_net"

bridgeモードについては以下の方の記事がわかりやすいです。

Docker network 概論 - Qiita

ボリュームの作成

replicationを行うのに必要なダンプを効率よく共有できるように共有するvolumeを準備します。
コマンドは以下です。

docker volume create --name shared-dump

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

docker volume ls -f "NAME=shared-dump"

コンテナの作成

今回は、現在最新バージョンである8.0.22を利用します。
以下のコマンドでイメージをpullしてください。

docker pull mysql:8.0.22

以下のコマンドでイメージがpullできていることを確認して下さい。

docker images mysql:8.0.22

MasterとSlaveのコンテナを一つずつ作成します。

# Master
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.50 \
           --name master \
           mysql:8.0.22

# Slave
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.100 \
           --name slave \
           mysql:8.0.22

MasterとSlaveによるコマンドの違いはほとんどありません。
一応、どちらがMasterでどちらがSlaveであるかをわかりやすくするためにコンテナの名前を変えています。異なる点としてはこのぐらいです。

ちょっとした検証でコンテナを立ち上げる際は、--rmオプションをつけて立ち上げるのが個人的に好きです(諸事情があって今回は付与できませんでしたが)。--rmオプションは停止と同時にコンテナを削除してくれるのでゴミが残らないところがポイントです。

設定ファイルを置き換える

設定ファイルの置き換えがrunサブコマンドでできればいいんですけどね。

Dockerfileやdocker-composeだとこの辺りの処理がとても楽ですよね。
この辺りがコマンドだと辛いですねー。

というわけで、レプリケーションに必要なserver-idの設定値をそれぞれの環境に入れていきたいと思います。

# master
docker exec master bash -c "echo 'server-id = 101' >> /etc/mysql/my.cnf"

# slave
docker exec slave bash -c "echo 'server-id = 102' >> /etc/mysql/my.cnf"

設定を更新するためにmysqlを再起動する必要があります。
ただ、dockerを再起動するとコンテナは停止しちゃうのでコンテナの再起動が必要です。
そのため今回は--rmオプションが使えませんでした。

以下のコマンドを実行して再起動します。

docker restart $(docker ps -aq -f "NAME=master" -f "NAME=slave")

確認には以下を実行してみてください。

for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'select @@server_id;'"
done

レプリケーション

レプリケーション以外の環境は概ねできました。

ここからはあまりdocker関係ないですが、レプリケーションの検証まで行ってみます。

検証用にデータベースを作成する

for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'create database repl;'"
done

レプリケーションを開始するための準備

ここから先の作業はcommandを渡して作業すると面倒なのでコンテナに入ることにします。

# 以下のコマンドでmasterコンテナに入ります。
docker exec -it master bash

# rootユーザでmysqlに入ります。
mysql -uroot -proot

適当なテーブルを作成し、データを投入します。

-- 適当なデータを投入します。
create table repl.users (id int unsigned not null auto_increment, name varchar(255) not null, primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into repl.users (name) values ("sample taro"), ("sample hanako");

-- slaveが接続するために必要なレプリケーションユーザを作成します
create user 'repl'@'192.168.220.100' identified with mysql_native_password by 'pQ8gDdzQHEtX@b8';
grant replication slave, replication client on *.* to 'repl'@'192.168.220.100';

-- 現在のマスターの状態を確認します。FileとPosition列の値をメモしてください
show master status;

quit

ダンプを取得します。

mysqldump -uroot -proot repl> /dump/repl_dump.sql

レプリケーションを開始する

次はslaveコンテナでの操作となりますので、まずはコンテナに入りましょう。

docker exec -it slave bash

レプリケーションを開始するまでの流れとしては以下です。

  1. dumpを取り込む
  2. レプリケーションの設定を行う
  3. レプリケーションを開始する

まずは、ダンプを取り込みます。

mysql -uroot -proot repl < /dump/repl_dump.sql

レプリケーションの設定を行う前にmasterと差分があることを確認しましょう(mysqlへログインしている前提です)。
masterでの作業でメモした「File」と「Position」の値を利用します。

-- replicationの設定を行います。
change master to
master_host='192.168.220.50',
master_user='repl',
master_password='pQ8gDdzQHEtX@b8',
master_log_file='binlog.000003',
master_log_pos=1518;

-- レプリケーションを開始します。
start replica;

start slaveコマンドは消されるようで今後はstart replicaを使うようです。

同期を確認する

現在のrepl.usersテーブルの状態は以下です。

mysql> select * from repl.users;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
+----+---------------+
2 rows in set (0.00 sec)

適当なデータを投入し、master,slave両方でテーブルの状態を確認してみます。

mysql> insert into repl.users (name) values ("takeshi"), ("tanaka"), ("sasaki");
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- master
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         101 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)

-- slave
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         102 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)

大丈夫そうですね。

最後に

本来はもう少しひねりたかったのですが、普通の検証になってしまいました(すみません)。

インフラ業務では、本番により近い環境で作業を行うかどうかで品質が大きく変わってしまいます。
なので、dockerをもっとうまく使いこなせればなーと日々感じますねー。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker for Windows で postgresのコンテナが起動できない

Docker for Windows で laradockのデータベースをpostgresにしたところ、うまくできない場合・・・

psqlで接続できない・・・

laradockを利用して、postgresにデータベースを作成する為に

docker-compose exec workspace psql -U default -h postgres

を実行したところ、以下のエラー

psql: could not translate host name "postgres" to address: No address associated with hostname

そもそもコンテナが起動してない・・・

docker ps してコンテナを確認すると、postgresは起動していない
docker-compose up -d で特にエラーが出ていなかったのだが・・・

docker ps

CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
1370b45bf0b5        nginx:alpine            "/docker-entrypoint.…"   40 minutes ago      Up 10 seconds       0.0.0.0:8082->80/tcp     laravel-sns_nginx_1
e92f90d8dd0f        laravel-sns_php-fpm     "docker-php-entrypoi…"   6 days ago          Up 12 seconds       0.0.0.0:9000->9000/tcp   laravel-sns_php-fpm_1
4fc742fdaba5        laravel-sns_workspace   "docker-php-entrypoi…"   6 days ago          Up 12 seconds       9000/tcp                 laravel-sns_workspace_1

laradockディレクトリ内の.env は永続化OK

DATA_PATH_HOST=../data

.envのDATA_PATH_HOSTがデフォルトの状態だとエラーになるという記事がヒットするが、私の環境は変更済み

はて?

解決!!!

laradockディレクトリのdocker-compose.yml を変更する事で解決しました!

  postgres:
    image: postgres:${POSTGRES_VERSION}-alpine
    depends_on:
      - php-fpm
    ports:
      - ${POSTGRES_PORT}:5432
    volumes:
#      - ${DATA_PATH_HOST}/postgres:/var/lib/postgresql/data   #変更前
      - ${DATA_PATH_HOST}/postgres:/var/lib/postgresql   #変更後
    environment:

vokumesのディレクトリ指定で最後のdataを削除

これで、再度、コンテナを起動

docker-compose up -d workspace php-fpm nginx postgres
Starting laravel-sns_workspace_1  ... done                                                                           Starting laravel-sns_php-fpm_1   ... done                                                                            Starting laravel-sns_nginx_1      ... done                                                                           Recreating laravel-sns_postgres_1 ... done  

でコンテナを確認

 docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
07773a4cbf64        postgres:11.6-alpine    "docker-entrypoint.s…"   6 seconds ago       Up 4 seconds        0.0.0.0:5432->5432/tcp   laravel-sns_postgres_1
1370b45bf0b5        nginx:alpine            "/docker-entrypoint.…"   44 minutes ago      Up 4 seconds        0.0.0.0:8082->80/tcp     laravel-sns_nginx_1
e92f90d8dd0f        laravel-sns_php-fpm     "docker-php-entrypoi…"   6 days ago          Up 6 seconds        0.0.0.0:9000->9000/tcp   laravel-sns_php-fpm_1
4fc742fdaba5        laravel-sns_workspace   "docker-php-entrypoi…"   6 days ago          Up 6 seconds        9000/tcp                 laravel-sns_workspace_1

無事にpostgresを起動できました!

しかし、同じ環境でmacのdockerだと問題なく動くのに、Docker for Windowsだと動かない原因までは追っていません!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rust,Wasm,Dockerで"hello world"をする MacOs catalina

Rust,Wasm,Dockerで"hello world"をする

Mac OS でRust,Wasmの環境を作ろうとしたのですが、

コマンド
cargo install cargo-generate

がエラーになり進めなかったため、dockerで環境構築しました。

環境構築の方法についてはこちら↓で記事にしております。

Rust,Wasmの環境をDockerで作る MacOS

今回は、ここから更に、簡単なwebページを作成して"hello world"するまでを解説いたします。

参考にした記事はこちら↓です。
ゼロからRust+WasmをFirebaseでデプロイするまでを簡単に

上記の記事をそのままやろうとしても上手くできないので、詰まったポイントについて解説いたします。

環境構築

こちらの記事で解説しています。

Rust,Wasmの環境をDockerで作る MacOS

テンプレートを取ってくる

コマンド
cargo generate --git https://github.com/rustwasm/wasm-pack-template
Project Name: wasm-lesson

src/lib.rs の内容を書き換える

元の関数名が「greet」なのを「aisatsu」に変更

src/lib.rs
mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
//pub fn greet() { ここを変更しています。
pub fn aisatsu() {
    alert("Hello, wasm-lesson!");
}

プロジェクトをビルド

コマンド
wasm-pack build

webページテンプレートを取ってくる

コマンド
npm init wasm-app www

index.jsを書き換える

wasm.greet()を「wasm.aisatsu()」に変更します。

index.js
import * as wasm from "hello-wasm-pack";

//www.greet() これを変更します。
wasm.aisatsu();

npmで依存関係を解決

コマンド
cd www
npm install

Wasmを index.html が読み込めるようにする

www/package.jsonのdependenciesにディレクトリを追加する。

package.json
{
  //...
  "devDependencies": {
      "wasm-lesson": "file:../pkg", // 追加
      // ...
  }
}

再び依存関係を解決します。

コマンド
npm install

実行するもエラーになる

ここでサーバーを立ち上げます

コマンド
npm run start

しかしブラウザを立ち上げてlocalhost:8080 するとエラーになってしまいます。

サーバーの設定ファイルを変更する

サーバーのホストとポートを変更するために、設定ファイルを開きます。

コマンド
vim node_modules/webpack-dev-server/bin/options.js

すると、host,portについての記載部分があるのでこちらを次のように変更します。

options.js
//~~省略~~
port:{
 default:'8080', //これを追記
 describe:'The port',
 group:CONNECTION_GROUP,
 }
//~~省略~~
host:{
 type:'string',
 // default:'localhost',
 default:'172.17.0.2', //これを追記
//~~省略~~

こうすることでホストのブラウザでも実行を確認できるようになります。
こちら↓の記事を参考にしました。
dockerコンテナ上でnode.jsのサーバを起動し、ホスト端末からアクセスする

もし、まだエラーになっている場合、dockerのポート設定が8080ではないか、
または172.17.0.2以外を指定する必要があるかもしれません。
上記のポストが参考になると思いますので参照してください。

再度、試してみる。

コマンド
npm run start

今度は成功です。ブラウザで動作を確認できるようになりました。

参考にした記事

Rust,Wasmの環境をDockerで作る MacOS

ゼロからRust+WasmをFirebaseでデプロイするまでを簡単に

dockerコンテナ上でnode.jsのサーバを起動し、ホスト端末からアクセスする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文系出身業務経験なしのプログラマーがDocker使ってみた

業務経験なしのプログラマーがDocker使ってみた

はじめに

今年9月まで学生で、それまで独学のプログラミング歴は約2年です。
Dockerを使い始めたのはある企業での学生用インターンがきっかけでした。
その時の経験や感じたことをまとめて同じ境遇の方に少しでもシェアできたらと思います。

Dockerについて

Dockerとは仮想環境を軽量でかつ短時間で構築できるオープンソースのソフトウェアサービスで、IaC(Infrastructire as Code)という概念のもと、複数人の開発にて発生するコンフリクトやバージョンの違いによるエラーを少なくする為に多くの企業で取り入れられているようです。実際には日本での使用はまだまだ浸透してないという声もあり、今後開発効率を上げる為にも導入を検討している所もあるようです。

Dockerについて学んだこと

  • Dockerfileというファイルがある
  • DockerImageというDockerHubで無償で提供された仮想環境がある(厳密にはコンテナ)
  • Docker-composeは便利である
  • Alpine LinuxというDockerに特化したLinuxのディストリビューションがある

Dockerfileというファイルがある

Dockerで仮想環境を構築する上で欠かせないDockerfile
前述した通りコードで管理する仮想環境なので、必要な情報をモジュールに書き出します。
書き方は下記の例を見てほしいです

FROM alpine:latest
RUN apk update
RUN apk add nginx php7 php7-fpm git bash vim curl lsof supervisor
などなど

上記例はwebアプリを開発するための環境でAlpineLinuxというOSにnginxやphp-fpmなど必要なパッケージをインストールしています。もちろんインストールパッケージは少なければ少ないほどより軽量な仮想環境となります。1行目にFROM alpine:latestというコードはDockerHubからalpineというDockerImageをlatest(最新版)として引っ張ってきています。こうすることでalpineというOSのもとで構築できるようになります。そのほかにもUbuntuやDebianなども指定できます。続いて2行目のRUN apk updateですが、RUNは実行という意味と捉えて良いでしょう。alpineを指定しているのでapkコマンドを使ってパッケージのインストールの為に最新版に更新しています。続いて3行目からは必要なパッケージのインストールです。これはなんとなくわかると思うので割愛します。このように非常にシンプルな書き方で環境構築の準備ができます。

ここで疑問に感じた人もいるでしょう。DockerHubとは?実態が見えてこないと思います。
そういう方は次を見てください。

DockerImageというDockerHubで無償で提供された仮想環境がある(厳密にはコンテナ)

DockerにはDockerHubという自身のDockerアカウントを管理するサイトhttps://hub.docker.com があります。Screen Shot 2020-12-01 at 21.00.12.png

ここの上部にある検索欄で既存の環境を検索してを引っ張ってくることができます。
試しにUbuntuを検索してみましょう。
Screen Shot 2020-12-01 at 21.01.17.png

このようにUbuntuのDockerImageが表示されました。右のLinux-x86(latest)と書いてあるのがバージョンです。
これをDockerfileで引っ張ってくると

FROM ubuntu:latest

となります。

ここで感の良い方は気づくはず。ここで紹介したイメージはOS単体でしたが、他にもより開発に便利なカスタマイズされたイメージがあってそれをインストールすれば自分で構築しなくて済むと。
そうなんです!
実はこのDocker、人が作成した環境を使ってそれを元に開発できるのです!
例えばこのイメージ
Screen Shot 2020-12-01 at 21.16.27.png

centOSに初めからmysqlが入ってます。(まぁ、基本的にデータベースは別コンテナにした方が良いですが..)
このように人の書いた環境を使うことができます。
これがDockerの利点の一つでしょう。

Docker-composeは便利である

続いてDocker-composeの紹介に入ります。
DockerではDockerfileからイメージ化されたDockerImageをdockerコマンドでコンテナ化させます。しかし、しかし複数のイメージを一度にコンテナ化させたり管理する時はdocker-composeコマンドを使うことをお勧めします。docker-composeを使用するための条件はdocker-compose.ymlというymlファイルを作成する必要があります。
例えば

version: "3"
services:
    ubuntu:
        image: ubuntu
        ports:
            - "80:80"
        volumes:
            - ./config/nginx.conf:/etc/nginx/nginx.conf
            - ./config/sub.conf:/etc/php7/php-fpm.d/sub.conf
            - ./config/php.ini:/etc/php7/conf.d/php.ini
            - ./config/default.conf:/etc/nginx/conf.d/default.conf
            - ./config/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf
        command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
        stdin_open: true
        tty: true
        container_name: alp-pha

全ての解説をすると莫大な文字数になりそうなのでざっくりとお話しします。
基本的な構文の構造はインデントを使って階層化しています。
まずversionなのですが、現段階では基本的に3にしといてください。servicesの欄に管理したいコンテナを記入します。(現時点ではコンテナは作成されていません) ubuntuという名前のコンテナを作成する為に先ほど引っ張ってきたUbuntuのイメージを指定してコンテナのポート番号など詳細設定をしていきます。volumesではファイルのマウントをすることでローカルとコンテナ内のファイルをつなぐことができます。commandやcontainer_nameなどの細かい設定は公式ドキュメントを読んだ方が早いでしょう。

完成したdocker-compose.ymlが置いてあるフォルダに移り、コンテナを作成します。

docker-compose up -d 

これでエラーがない限りコンテナが作成されるはずです。よくあるエラーではymlファイルの構文が間違っているので注意が必要です。ちなみに -d は バックグラウンドでコンテナを起動してくれますが、詳しくはこれも公式ドキュメントを読んでみた方が良いかもしれません。コンテナは作成したら勝手に起動しています。
作成されたコンテナは

docker ps 
もしくは
docker ps -a

で確認できます。(docker-composeコマンドでもOK)
続いて作成したコンテナに入ります。

docker-compose exec -it コンテナID bash

で入れます。コマンドIDはdocker psで確認したら書いてあります。 bashは例で、shだったり何かしらシェルを指定します。※ttyが割り当てられていないコンテナもあるので、それはdocker-compose.ymlで設定したりできます。コンテナの中に入ったらdocker-compose.ymlでマウントしたファイルだったりイメージでインストールしたパッケージがあったりが確認できるかと思います。
これでDockerによる環境は完成です。

Alpine LinuxというDockerに特化したLinuxのディストリビューションがある

AlpineというディストリビューションはDockerを使ってみて初めて知りました。Docker用に作られたのか定かではありませんが、なぜAlpineはDockerに特化しているのかというと、なんと言っても軽量さです。
UbuntuやDebianなどその他OSで作られた仮想環境は基本的に500MB以上だったりします。それをAlpineで構築すると5MB~100MBくらいで済むのです。従来のやり方では環境を構築するたびに要領を取るし時間もかかる、重いしなど不満爆発の状態でしたが、alpineの登場によってより便利になりました。
AlpineもDockerHubにあるので、皆さんもぜひ使ってみてください!

終わりに

初めてQiitaで記事を書かせていただきました。文章力ゼロだと思いますが、お許しください笑
Dockerについては他にも書きたいことがたくさんあるのですが、一度では書ききれないので、個人的に話したいことだけを選んで書きました。

さて、Dockerを使ってみての感想ですが、正直感動しました。文系出身業務経験なしのプログラマーですが、非常に直感的に使いやすいですし、チームでの開発を想定した場合、コンフリクトも減りそうです。
Dockerを扱えるのは需要のあるスキルですし、今後使えることが当たり前の時代になってくると思います。
学習難易度も高くないと思うので、ぜひ皆さんも使ってみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerを使用してさくっとPHP8を触ってみた

dockerを使用してさくっとPHP8を触ってみた

PARONYM Advent Calendar 2020 - Qiita の2日目です。

はじめに

2020/11/26にPHP8がリリースされました。
ということで、自身のローカル環境をなるべく汚さずにPHP8の新機能を触ってみることにしました。

前提条件

  • dockerがインストールされていること

きっかけ

PHP8の新機能を触ってみたい!でも自身のPCを汚したくない!
PHP8を試す環境として、当然ながらプロダクトの開発環境で試すわけにはいきません。(本番環境なんて以ての外)
となるとローカルPCで試すのが妥当ですが、インストールや設定の手間、もしもの時のバージョンの切り替えを考えると一気に面倒になります。
今回はPHP8の機能を触るのが目的であり速度性能を試すわけではないため、さくっとdockerを使用してPHP8を実行することにしました。

コード

今回試したコードは全てGithubにコミットしています。
https://github.com/y-kakinuma-paronym/php8-docker

dockerでPHP8の実行

$ docker run --rm -v `pwd`:/app -w /app php:8.0-rc php {実行したいファイル}
  • PHPの実行後、コンテナは削除する : --rm
  • カレントディレクトリをコンテナの/appにマウント : -v `pwd`:/app
  • コンテナ内の作業用ディレクトリは/app : -w /app
  • dockerイメージは php:8.0-rc を使用
  • php実行 : php {実行したいファイル}

毎回コマンドを打つのは面倒なのでシェルスクリプトを作成しておくと楽です。

exec_php.php
#!/bin/sh
docker run --rm -v `pwd`:/app -w /app php:8.0-rc php $1

# ./exec_php.sh match_expression.php   

PHP8新機能

PHP8の新機能はここから抜粋しています。
PHP: PHP 8.0.0 Release Announcement

Named arguments

引数を渡すときに名前を指定できるようになりました。
https://wiki.php.net/rfc/named_params

<?php
// Named arguments
// https://wiki.php.net/rfc/named_params

$str = "<a href='test'>Test</a>";

// PHP7
// $result = htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);

// PHP8
// double_encodeの引数のみをデフォルト引数から変更したい場合、引数の名前を指定する
$result = htmlspecialchars($str, double_encode: false);
echo $result . "\n"; // &lt;a href='test'&gt;Test&lt;/a&gt;

?>

Constructor property promotion

コンストラクタでのプロパティ初期化が簡略化できるようになりました。
https://wiki.php.net/rfc/constructor_promotion

<?php
// Constructor property promotion
// https://wiki.php.net/rfc/constructor_promotion

class Point {
    // PHP8
    public function __construct(
        public int $x = 0,
        public int $y = 1,
        public int $z = 2,
    ) {
        print "Point constructor\n";
    }

    // PHP7
    // public int $x;
    // public int $y;
    // public int $z;
    // public function __construct(
    //     int $x = 1,
    //     int $y = 2,
    //     int $z = 3
    // ) {
    //     $this->x = $x;
    //     $this->y = $y;
    //     $this->z = $z;
    // }

    public function showClassParameter() {
        print_r([$this->x, $this->y, $this->z]);
    }
}

$obj = new Point();
$obj->showClassParameter();

?>

Union types

共用体型(union type)が追加されました。
https://wiki.php.net/rfc/union_types_v2

<?php
declare(strict_types=1);

// int と string を型指定
$f = function (int|string $v) {
    var_dump($v);
};

$f(100); // ok
$f("abc"); // ok
$f(true); // ng

Match expression

https://wiki.php.net/rfc/match_expression_v2

<?php
// Match expression
// https://wiki.php.net/rfc/match_expression_v2

switch (1) {
    case 0:
        $result = "Foo\n";
        break;
    case 1:
        $result = "Bar\n";
        break;
    case 2:
        $result = "Baz\n";
        break;
}
echo $result; // Bar

/**
 * switchとの違い
 * ・一致した結果を変数に代入することが可能
 * ・一致は一つのみをサポート。breakなどは必要なし
 * ・一致は厳密な比較
 */

echo match(1) {
    0 => "Foo\n",
    1 => "Bar\n",
    2 => "Baz\n"
};
// Bar

echo match (8.0) {
    '8.0' => "Oh no!",
    8.0 => "This is what I expected",
};
// This is what I expected
// ↑厳密な比較のため

?>

switchとの違いについての説明はコード内に記載しています。特に注意が必要なのが、一致は厳密な比較であるところです。

Nullsafe operator

null演算子が追加されました。
https://wiki.php.net/rfc/nullsafe_operator

<?php
// Nullsafe operator
// https://wiki.php.net/rfc/nullsafe_operator

class Session
{
    public $user = null;
}

$session = new Session();

/**
 * nullsafe演算子「?」を用いることでチェーン内の1つの要素で評価が失敗すると、
 * チェーン全体の実行が中断され、nullを返す
 */
$country = $session->user?->getAddress()?->country;
var_dump($country) // null

?>

$session->user?->getAddress()?->country;
例では↑のuserがnullのため失敗となり、nullsafe演算子はnullを返します。

PHP8の所感

PHP8の目玉はなんと言ってもJITコンパイラーによる処理の高速化ですが、こういった細かい機能も実装が楽になるため嬉しい追加でした。
特にMatch expressionは、JSONを作成するためにarrayを組み立てるときに三項演算子より複雑な分岐ができるため非常に便利だと思います。

$result = 1;
$json = [
    "resutl" => match($result) {
        0 => "Failed",
        1 => "Success",
        2 => "No Updated"
    },
];

echo json_encode($json); // {"resutl":"Success"}

Nullsafe operatorもうまく利用できれば、if文が減りコードの可読性を向上させることができそうです。

まとめ

今回はPHP8の新規機能をDockerを使用して自身のPCをなるべく汚さずに試してみました。
今回紹介していないPHP8の新機能はまだまだありますので、公式サイトを参照してください。
PHP: PHP 8.0.0 Release Announcement

Dockerを利用すれば、PHP8に限らず様々な環境を楽に構築できるのでこれからもどんどん利用していきたいと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UDPのマルチキャストをDockerコンテナ・ネットワーク上で動かしてみた

※半年ほど前に書いた記事をQiitaに持ってきました

JavaのMulticastSocketを使って、UDPのマルチキャストを送受信するクライアントを作り、Dockerコンテナ・ネットワーク上で動かしてみようと思います。

今回書いたコードはこちらのリポジトリに置いてあります。

UDPのマルチキャストとは

コネクションの確立をせず、パケットを一方的に送りつけることが特徴なUDPですが、以下のような送信方式があります。

  • ユニキャスト
  • ブロードキャスト
  • マルチキャスト

ユニキャストは、特定の一台のホストに対して送信します。ブロードキャストは、あるネットワークに属する全てのホストに対して送信します。マルチキャストは、あるネットワークに属する特定のホスト(一台でも複数台でもよい)に対して送信します。

マルチキャストにおいて、送信側はマルチキャストアドレスを宛先アドレスとして指定してデータを送信します。マルチキャストアドレスはIPアドレスクラスのうち、クラスDのIPアドレスです。受信側はこのマルチキャストアドレスにJoinすることでデータを受け取ることができるようになります。

また、ユニキャストを複数台に行うことに対して、マルチキャストを行うことのメリットは以下のようになります。

  • 宛先のIPアドレスを知らなくても、マルチキャストアドレスだけ分かっていれば、Joinしているホスト全員に対して送信できる
  • 一つのパケットで複数台に向けて送信できるので通信効率が良く、送信元の負荷が低い

Dockerコンテナ・ネットワークとは

Dockerコンテナ・ネットワークとは、Dockerコンテナが属するネットワークのことです。docker network lsコマンドを実行すると一覧が表示されます。今回はマルチキャストを検証するために、同一ネットワーク内にコンテナを立てなければなりません。しかし、実は難しいことをする必要はなく、docker-composeを使えば自動でネットワークを作成してくれるます。なので、今回はdocker-composeを使って複数のクライアントを動かそうと思います。

簡単なチャットアプリを作ってみる

それではUDPのマルチキャストを送受信するクライアントとして、今回は簡単なチャットアプリを作ってみます。一つのクライアントで送信と受信の両方を行うようにしてみます。

ソースコード

MulticastClient.javaというファイルに以下を記述します。

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Scanner;

class Sender extends Thread {
 private final int toPort;
 private final String mcastAddrName;

 public Sender(int toPort, String mcastAddress) {
   this.toPort = toPort;
   this.mcastAddrName = mcastAddress;
 }

 public void run() {
   Scanner scan = null;
   MulticastSocket socket = null;
   try {
     InetAddress mcastAddress = InetAddress.getByName(mcastAddrName);
     scan = new Scanner(System.in);
     socket = new MulticastSocket();

     String message;
     while ( (message = scan.nextLine()).length() > 0 ) {
       byte[] bytes = message.getBytes();
       DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort);
       socket.send(packet);
     }
   } catch (IOException e) {
     e.printStackTrace();
   } finally {
     if (scan != null) scan.close();
     if (socket != null) socket.close();
   }
 }
}

class Receiver extends Thread {
 private final int fromPort;
 private final String mcastAddrName;
 private static final int PACKET_SIZE = 1024;

 public Receiver(int fromPort, String mcastAddrName) {
   this.fromPort = fromPort;
   this.mcastAddrName = mcastAddrName;
 }

 public void run() {
   MulticastSocket socket = null;
   byte[] buf = new byte[PACKET_SIZE];
   DatagramPacket packet = new DatagramPacket(buf, buf.length);

   try {
     socket = new MulticastSocket(fromPort);
     InetAddress mcastAddress = InetAddress.getByName(mcastAddrName);
     socket.joinGroup(mcastAddress);

     while (true) {
       socket.receive(packet);
       String message = new String(buf, 0, packet.getLength());
       System.out.println(packet.getSocketAddress() + " : " + message);
     }
   } catch (IOException e) {
     e.printStackTrace();
   } finally {
     if (socket != null) {
       socket.close();
     }
   }
 }
}

public class MulticastClient {
 public static final int PORT = 3000;
 public static final String MCAST_ADDR = "224.0.1.1";

 public static void main(String args[]) {
   Receiver receiver = new Receiver(PORT,  MCAST_ADDR);
   Sender sender = new Sender(PORT,  MCAST_ADDR);
   receiver.start();
   sender.start();
 }
}

ソースコードの説明

上記のコードには以下の3つのクラスが含まれています。

  • Senderクラス
  • Receiverクラス
  • MulticastClientクラス

それぞれについて説明します。

Senderクラス

Senderクラスは、データを送信するためのクラスです。受信と並列して動かすためにThreadクラスを継承して、runメソッドをオーバーライドしています。

フィールド

 private final int toPort;
 private final String mcastAddrName;

toPortは送信先のポート番号(受信側が受信するポート番号)です。mcastAddrNameは送信先のマルチキャストアドレスの名前です。

コンストラクタ

 public Sender(int toPort, String mcastAddress) {
   this.toPort = toPort;
   this.mcastAddrName = mcastAddress;
 }

変数を初期化します。

runメソッド

 public void run() {
   Scanner scan = null;
   MulticastSocket socket = null;
   try {
     InetAddress mcastAddress = InetAddress.getByName(mcastAddrName);
     scan = new Scanner(System.in);
     socket = new MulticastSocket();

     String message;
     while ( (message = scan.nextLine()).length() > 0 ) {
       byte[] bytes = message.getBytes();
       DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort);
       socket.send(packet);
     }
   } catch (IOException e) {
     e.printStackTrace();
   } finally {
     if (scan != null) scan.close();
     if (socket != null) socket.close();
   }
 }

whileループ内だけ説明します。message = scan.nextLine()では標準入力から一行読み取って、String messageに入れます。byte[] bytes = message.getBytes()では、messageをバイト配列に変換します。DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort)では、上記のバイト配列、宛先のマルチキャストアドレス、宛先のポート番号を指定し、DatagramPacketを生成します。socket.send(packet)でパケットを送信します。

Receiverクラス

Receiverクラスは、データを受信するためのクラスです。送信と並列で動かすためにThreadクラスを継承して、runメソッドをオーバーライドしています。

フィールド

 private final int fromPort;
 private final String mcastAddrName;
 private static final int PACKET_SIZE = 1024;

fromPortはパケットを受け取るポート番号です。mcastAddrNameはJoinするマルチキャストアドレスです。PACKET_SIZEは受け取るパケットのサイズです。

コンストラクタ

 public Receiver(int fromPort, String mcastAddrName) {
   this.fromPort = fromPort;
   this.mcastAddrName = mcastAddrName;
 }

変数を初期化します。

runメソッド

 public void run() {
   MulticastSocket socket = null;
   byte[] buf = new byte[PACKET_SIZE];
   DatagramPacket packet = new DatagramPacket(buf, buf.length);

   try {
     socket = new MulticastSocket(fromPort);
     InetAddress mcastAddress = InetAddress.getByName(mcastAddrName);
     socket.joinGroup(mcastAddress);

     while (true) {
       socket.receive(packet);
       String message = new String(buf, 0, packet.getLength());
       System.out.println(packet.getSocketAddress() + " : " + message);
     }
   } catch (IOException e) {
     e.printStackTrace();
   } finally {
     if (socket != null) {
       socket.close();
     }
   }
 }

whileループの中だけ説明します。socket.receive(packet)でパケットの受信を待機します。パケットを受信したらString message = new String(buf, 0, packet.getLength())によりバイト配列をStringに変換します。そしてSystem.out.println(packet.getSocketAddress() + " : " + message)により、標準出力に出力します。

MulticastClientクラス

MulticastClientクラスでは、mainメソッドを定義しています。

フィールド

 public static final int PORT = 3000;
 public static final String MCAST_ADDR = "224.0.1.1";

SenderクラスとReceiverクラスに渡すポート番号とマルチキャストアドレスを定義しています。

mainメソッド

 public static void main(String args[]) {
   Receiver receiver = new Receiver(PORT,  MCAST_ADDR);
   Sender sender = new Sender(PORT,  MCAST_ADDR);
   receiver.start();
   sender.start();
 }

ReceiverクラスとSenderクラスをインスタンス化し、Threadをstartさせています。

検証環境を準備する

上記のチャットアプリを動かす環境をDockerで構築します。同一ネットワーク内に複数のクライアントを立ち上げるためにdocker-composeファイルを定義します。

Dockerfile

Dockerfileは以下のようになります。

FROM openjdk:14
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac MulticastClient.java
CMD ["/bin/bash"]

Imageはopenjdk:14を使います。javac MulticastClient.javaでコンパイルした後、/bin/bashでbashを開きます。実行するときはdocker container exec -it [CONTAINER ID] bashで中に入ってから、java MulticastClientを叩きます。

本当はCMD ["/bin/bash"]のところをCMD ["java", "MulticastClient"]として実行しておき、containerの中に入りたかったのですが、やり方がわかりませんでした。

docker-compose

docker-compose.ymlは以下のようになります。

version: '3'
services:
 client1:
   build:
     context: .
     dockerfile: Dockerfile
   tty: true
 client2:
   build:
     context: .
     dockerfile: Dockerfile
   tty: true

先ほどのDockerfileからImageをBuildします。tty: trueを記述しておくとコンテナが起動したままになります。

client1と同じように、client2, client3...とコンテナを作成します。これらのコンテナはdocker-compose upを実行すると自動で作成されるnetworkに属することになります。プライベートIPアドレスは自動で割り振られます。

検証してみる

それではクライアントを複数立ち上げてマルチキャストが動くか検証してみます。

コンテナを起動させるために以下のコマンドを実行します。

docker-compose up --build

コンテナに入るには以下のコマンドを実行します。CONTAINER IDはdocker container lsで調べることができます。

docker container exec -it [CONTAINER ID] bash

アプリケーションを実行するにはコンテナ内で以下のコマンドを実行します。

java MulticastClient

3つコンテナを作って、それぞれクライアントを実行してみたのが以下の画像です。ターミナルを横に3つ並べています。

20200609202314.png

左のターミナルでHello!と入力すると、他のターミナルにもメッセージが表示されました!左のターミナルで実行しているコンテナのプライベートIPアドレスは172.29.0.3だということも分かりますね!

20200609202323.png

真ん中のターミナルでHello!Hello!と入力しても他のターミナルに表示されます。真ん中は172.29.0.2ですね。

20200609203052.png

右のターミナルも同じく動きます。

20200609203100.png

感想

今回はJavaのMulticastSocketを使って、UDPのマルチキャストを送受信するチャットクライアントを作り、Dockerコンテナ・ネットワーク上で動かしてみました。UDPについて調べる過程でかなり勉強できたので良かったです。また、Dockerの動かし方についても学べたので一石二鳥でした!

参考

https://www.techscore.com/tech/Java/JavaSE/Network/5/

https://milestone-of-se.nesuke.com/nw-advanced/multicast/multicast-summary/

https://qiita.com/yosisa/items/a5670e4da3ff22e9411a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerだけでnpm initやnpm installをする方法

npmをローカルにインストールせずに、Dockerだけでnpm initnpm installをする方法を解説します。

npm init

まずは、ディレクトリを作成して中に移動します。

mkdir app && cd app

次に、dockerを使ってnpm initします。以下のコマンドを実行してください。実行するとpackage.jsonが生成されます。

docker run --rm -w "/usr/app" -v "${PWD}:/usr/app" node:15.3-alpine npm init -y

コマンドの解説をします。

docker run <IMAGE>は、イメージをコンテナ化するコマンドです。今回はnode:15.3-alpineを指定しています。これは、公式のイメージで、ローカルになければコマンドを実行したときにインストールされます。

--rmオプションは、コンテナの終了時に自動的にそのコンテナを削除します。詳しくはこちらに書いてあります。

-wオプションでワーキングディレクトリを指定しています。システムの邪魔にならなければどこでもいいですが、今回は/usr/appとしました。

-vオプションは、マウントをします。今回は、${PWD}:/usr/appとしているので、現在のディレクトリを/usr/appにマウントすることになります。こうすることで、npm init -yを実行して、/usr/app内に生成されるpackage.jsonを現在のディレクトリに反映させることができます。

最後の引数は、実行したいコマンドです。

npm install

上記と同じように以下のコマンドを実行します。例としてexpressをインストールしてみました。実行するとpackage-lock.jsonnode_modulesが生成されます。

docker run --rm -w "/usr/app" -v "${PWD}:/usr/app" node:15.3-alpine npm install express

Dockerを使うことで、npmをインストールせずに使うことができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでCGI #2: ニコニコチャンネル キャッシュサーバー 「nicochcgi」

はじめに

DockerでCGI、その2。
順番としてはこっちが最初。苦労しました。

前:DockerでCGI #1: EPWING電子辞書サーバー「let me see...」 (2003年)

nicochcgi

nicochcgiはニコニコチャンネルをスクレイピング・キャッシュしてコメント付きで見るCGIサーバーです。

GitHub : nicochcgi_docker
Docker : ghcr.io/kurema/nicochcgi/nicochcgi

image.png

Microsoft Storeに専用アプリもあります。
Japanese badge

サーバーサイドはPerl+CGI、クライアントは素のHTML5+JavaScriptという(一昔前の)普通の構成です。Ajax時代のLAMP。
定期実行はcron。サムネイル作成はシェルスクリプト。
今なら基本ASP.NET Coreで作りますが、当時サーバーにしていたx86版Ubuntuでは使えなかったので手軽に作ってこうなりました。
2017年ごろから気が向いたときに開発を続けています。

導入方法

導入方法は以下です。
あらかじめニコニコ動画のアカウントが必要です。

$ git pull https://github.com/kurema/nicochcgi_docker.git
$ cd nicochcgi_docker
$ nano docker-compose.yml # キャッシュ場所の変更・移動先にはmkthumb.shもコピー
$ sudo docker-compose up -d
$ chmod 666 config/*
$ chmod 777 videos/*.sh
# 管理用パスワードの作成・各種設定
$ sudo docker-compose exec nicochcgi perl /var/www/html/get_password.pl
$ nano config/nicoch.conf
# cronの設定
$ sudo crontab -e
0 3 * * * cd docker-compose.ymlの場所 && docker-compose exec -T nicochcgi perl /var/www/html/nico-anime.pl >> ログファイル 2>&1 && docker-compose exec -T nicochcgi perl /media/niconico/mkthumb.sh >> ログファイル 2>&1

この後、http://server:50001/でログインし、キャッシュするチャンネルを登録してください。

開発過程

初めてのDockerだったので入門記事やらを読みながら試行錯誤しました。
nicochcgi自体の開発は以前行ったものなので省略します。

ベースイメージはubuntuです。
ffmpegが数百MBを消費するので多少節約したところで誤差でしょう。httpdよりトラブルが少なそうだと判断しました。

開発は基本的にGitHub Actionsをぐるぐる回す形でやりました。無料なので。
ローカルで試さないままリモートでやってみるという形です。
マルチプラットフォームビルドは重いのでビルド中は別の作業ができるのは楽でした。

Docker対応

Dockerfile: GitHub, Current

Dockerfile
Dockerfile
FROM ubuntu

MAINTAINER kurema

# https://qiita.com/Kashiwara/items/07e154bb5e859445eac6
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2

#You need to install tzdata first to prevent 'Please select the geographic area...' message.
#https://sleepless-se.net/2018/07/31/docker-build-tzdata-ubuntu/
RUN apt-get update -y && \
    apt-get install -y --no-install-recommends tzdata

#Timezone is set to Japan assuming you are in Japan.
ENV TZ=Asia/Tokyo
#Install dependencies.
RUN apt-get install -y --no-install-recommends ffmpeg \
      apache2 \
      cpanminus \
      build-essential \
      libexpat1-dev \
#fix cpanm error...
      liblwp-protocol-https-perl \
      libnet-ssleay-perl \
      libcrypt-ssleay-perl \
      openssl \
      libssl-dev && \
    apt-get clean && \
    rm -rf /var/cache/apt/archives/*
#Enable cgi. This is cgi in 2020.
RUN sed -ri 's/Options Indexes FollowSymLinks/Options Indexes FollowSymLinks ExecCGI/g;' /etc/apache2/apache2.conf && \
    echo "AddHandler cgi-script .cgi" >> /etc/apache2/apache2.conf && \
    a2enmod cgid && \
    echo "-- Apache2.conf --" && \
    cat /etc/apache2/apache2.conf

#Copy cpanfile first for better cache management.
RUN touch /var/www/html/cpanfile
COPY nicoch/cpanfile /var/www/html/cpanfile
RUN cpanm --installdeps --no-man-pages /var/www/html/ && \
    rm -rf /root/.cpanm/work/*

COPY nicoch/ /var/www/html/
RUN chmod 755 /var/www/html/*.cgi /var/www/html/*.pl

#/etc/nicochcgi/ should be overwritten using -v
COPY config/ /etc/nicochcgi/
RUN chown www-data:www-data /etc/nicochcgi/*
RUN chmod 666 /etc/nicochcgi/*

#I think document should be included.
COPY README.md /

EXPOSE 80
CMD ["apachectl", "-D", "FOREGROUND"]

Apacheのインストール

これは検索でヒットした以下の記事を参照しました。
環境変数の用意が必要なようです。
apt-getするだけだろうと調べなければトラブルでした。良かった。

@Kashiwara (2018) 「Dockerでapache2起動」Qiita

タイムゾーン設定

前回の記事に書きましたが、Please select the geographic area in which you live.のエラー対策にtzdataを先にインストールします。
サーバーの性質上、タイムゾーンは日本のはず。

Dockerfile
RUN apt-get update -y && \
    apt-get install -y --no-install-recommends tzdata
ENV TZ=Asia/Tokyo

CGI有効化

ベースコンテナによってCGI有効化の手順が違います。今回は以下の条件です。

  • トップページがindex.htmlDirectoryIndexは変更不要
  • CGIの拡張子は全て.cgi
  • Ubuntuなのでa2enmodが普通に使える。

したがって、Dockerfileでは以下になりました。

Dockerfile
RUN sed -ri 's/Options Indexes FollowSymLinks/Options Indexes FollowSymLinks ExecCGI/g;' /etc/apache2/apache2.conf && \
    echo "AddHandler cgi-script .cgi" >> /etc/apache2/apache2.conf && \
    a2enmod cgid

a2enmod cgidで有効化されるのは以下の2つです。

cgid.conf
# Socket for cgid communication
ScriptSock ${APACHE_RUN_DIR}/cgisock
cgid.load
LoadModule cgid_module /usr/lib/apache2/modules/mod_cgid.so

a2enmodと同じことをhttpdのようなa2enmodがないベースコンテナでやりたい場合は、上の二行を追記すればいいわけですね。
(#1ではScriptSockを忘れてちょっと困りました。)

a2enmod cgi

記事を書いている間に気づきましたが、誤ってa2emod cgi.loadになっていました。
実際にはマルチスレッドなのでa2enmod cgidにする必要があります。
それに対しa2enmodは勝手に判断してcgidを有効にしてくれていました。
mods-enabledmods-availableからのリンクを作ってくれるだけのお手軽ツールだと思ってましたが、なかなか賢いですね。
2020-10-24T05:47:59.1426858Z #21 0.362 Your MPM seems to be threaded. Selecting cgid instead of cgi.
2020-10-24T05:47:59.1427458Z #21 0.362 Enabling module cgid.
2020-10-24T05:47:59.1427912Z #21 0.367 To activate the new configuration, you need to run:
2020-10-24T05:47:59.1428339Z #21 0.367   service apache2 restart

cpanm

以前は自分のサーバーを主眼において、開発過程で必要なライブラリを都度CPANからインストールする形にしていました。
第三者がnicochcgiをインストールする際は同様に一々確認し追加していく想定でしたが、大変面倒で時間が掛かります。

まずcpanfileを作成しました。最初からそうするべきでしたね。

Docker側としては、キャッシュ管理のためにcpanfileのみをコピーしてcpanm --installdeps --no-man-pagesするだけ…のはずでした。

Dockerfile
RUN touch /var/www/html/cpanfile
COPY nicoch/cpanfile /var/www/html/cpanfile
RUN cpanm --installdeps --no-man-pages /var/www/html/ && \
    rm -rf /root/.cpanm/work/*

cpanmの苦労 #1: 重い

そもそもCPANのインストールは重いです。本当に重い。
一々テストやらビルドやらするからのようです。
apt installと違って実際にCPUを使って処理するからか、マルチプラットフォーム対応時には特に大変です。

ある時はGitHub Actionsで6時間処理をしたあげく自動キャンセルされました。そんなのあるんですね。
4時間応答なしだったので処理時間の問題でもなさそうですが。メモリ不足でスワップ?

image.png
image.png

参考までに成功時のQEMUでの実行時間を表にします。
同時処理なので参考値ですが、エミュレーション時は目安で10倍程度時間が掛かるようですね。

arch ellapsed
amd64 (Native) 449.1s
arm64 3921.1s
ppc64le 4014.9s
arm/v7 3919.5s

cpanmの苦労 #2: 依存関係

CPANはPerlモジュールだけ面倒を見るだけで、依存するバイナリパッケージを予め導入する必要があります。
今回は以下のエラーに悩まされました。
要するにLWP::Protocol::httpsインストール関係の問題です。

2020-10-23T19:00:01.5141020Z #24 2933. Building and testing IO-Socket-SSL-2.068 ... ! Installing IO::Socket::SSL failed. See /root/.cpanm/work/1603476565.7/build.log for details. Retry with --force to force install it.
2020-10-23T19:00:01.6639377Z #24 3037. ! Installing the dependencies failed: Module 'IO::Socket::SSL' is not installed, Module 'IO::Socket::SSL::Utils' is not installed
2020-10-23T19:00:01.6640902Z #24 3037. ! Bailing out the installation for LWP-Protocol-https-6.09.
2020-10-23T19:00:01.7953327Z #24 3037. ! Installing the dependencies failed: Module 'LWP::Protocol::https' is not installed
2020-10-23T19:00:01.7954896Z #24 3037. ! Bailing out the installation for /var/www/html/.
2020-10-23T19:00:01.7955267Z #24 3037. FAIL

最終的にはずらずらと以下全てをapt-getで導入し解決しました。cpanmも早くなりました。
要するにliblwp-protocol-https-perl他を導入すればcpanのテストをスキップし諸々導入してくれるようです。
実際には不要なものもある気がします。
検索してもドンピシャの答えがなかったので試行錯誤しました。

RUN apt-get install -y --no-install-recommends libexpat1-dev \
#fix cpanm error...
      liblwp-protocol-https-perl \
      libnet-ssleay-perl \
      libcrypt-ssleay-perl \
      openssl \
      libssl-dev

cpanmの苦労 #3: ログが面倒

上のログをよく見るとSee /root/.cpanm/work/1603476565.7/build.logとの記述があります。
ローカルでDocker buildをしているなら中に入ってファイルを確認できますが、GitHub Actions+マルチプラットフォームビルドとなると正直お手上げです。
思いつく対策は3つです。

  • ローカルで試してみる。
    • 今回はcpanfileに追加後ビルドに失敗するようになったので以前のバージョンがありました。
  • cpanm --installdeps -v (verboseのv)
  • Artifactを使うQiita記事
    • QEMUを使ってマルチプラットフォームでビルドしていて大変そうなので辞めました。

まずはローカルで試しました。docker-compose exec nicochcgi bashで入って、cpanfileを編集後cpanm --installdepsするだけの作業です。
試行錯誤をするには最適ですが、ローカルで動くはずがビルドに失敗したりしました。
なので-vで原因追及をしました。

-vはGitHub Actionsのログが長くなるので正常動作するならお勧めしません。
普通に動くようになれば削除しました。
本来はArtifactを使うべきだと思います。

公開

#1と同じく最終的にはGitHub Container Registryに保存しました。
しかし、最初なので以下のミスをしてしまいました。

  • 最初にGitHub Package Registryを使ってしまう。
    • GitHub Container Registryと異なりGitHub Package Registryではパブリックイメージは削除できません。ダウンロード数が0でも。
    • 古いバージョンが残ってしまいました。仕方ない。
    • 名前が似ているのでよくわかってなかった。
  • GitHub Container Registryでは「レポジトリ名/イメージ名」みたいなルールだと勘違いする。
    • その結果イメージ名がnicochcgi/nicochcgiという冗長な感じに。
    • 現時点の利用者はおそらく自分だけですが、名前変更は避けました。変えるのは簡単ですが履歴も消えますし。

記事時点でGitHub Package RegistryのDockerの奴とGitHub Container Registryがあるので皆さん気を付けましょう。

GitHub Container Registryのバグ?

ちなみに記事投稿時点でタグの付いていないイメージが一覧に見えますが何でしょうねこれ。
image.png

GitHub Actions

GitHub Actionsは前回同様GitHub Communityの例文を参照しました。というかこっちが先です。

.github/workflows/docker.yml: GitHub, Current

.github/workflows/docker.yml
.github/workflows/docker.yml
name: Docker Container Build Workflow

#https://github.community/t/github-package-registry-does-not-support-multi-cpu-architecture-image/14339/11

on:
  schedule:
    - cron: '0 8 13 * *' # once a month
  push:
    branches: 
      - main
    tags:
      - 'v*.*.*'
  pull_request:
    branches: 
      - main
  workflow_dispatch:

env:
  IMAGE_NAME: nicochcgi
  REPO_NAME: nicochcgi

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v2

      - name: Prepare
        id: prep
        run: |
          DOCKER_IMAGE=ghcr.io/${{ github.repository_owner }}/${REPO_NAME}/${IMAGE_NAME}
          VERSION=edge
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            VERSION=${GITHUB_REF#refs/tags/v}
          fi
          if [ "${{ github.event_name }}" = "schedule" ]; then
            VERSION=monthly
          elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            VERSION=test
          fi
          TAGS="${DOCKER_IMAGE}:${VERSION}"
          if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
            TAGS="$TAGS,${DOCKER_IMAGE}:latest"
          fi
          echo ::set-output name=tags::${TAGS}
          echo "::set-output name=latest::${DOCKER_IMAGE}:latest"
          # lowercase the branch name
          BRANCH=$(echo ${GITHUB_REF##*/} | tr '[A-Z]' '[a-z]')
          LABELS="org.opencontainers.image.revision=$GITHUB_SHA"
          LABELS="$LABELS,org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
          LABELS="$LABELS,org.opencontainers.image.version=$VERSION"
          LABELS="$LABELS,com.github.repo.branch=$BRANCH"
          LABELS="$LABELS,com.github.repo.dockerfile=Dockerfile"
          echo ::set-output name=labels::${LABELS}
          BUILD_ARGS="BRANCH=$BRANCH"
          echo ::set-output name=args::${BUILD_ARGS}
      - name: Tag names
        run: echo ${{ steps.prep.outputs.tags }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@master
        with:
          platforms: all

      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@master

      - name: Builder instance name
        run: echo ${{ steps.buildx.outputs.name }}

      - name: Available platforms
        run: echo ${{ steps.buildx.outputs.platforms }}

      - name: Login to GHCR
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v1 
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_PAT }}

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          builder: ${{ steps.buildx.outputs.name }}
          context: .
          file: ./Dockerfile
          platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/arm/v7
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.prep.outputs.tags }}
          build-args: ${{ steps.prep.outputs.args }}
          labels: ${{ steps.prep.outputs.labels }}
          cache-from: type=registry,ref=${{ steps.prep.outputs.latest }}
          cache-to: type=inline

時間

ビルド時間はキャッシュありで1~2分、フルのマルチプラットフォームビルドには1時間強程度です。
amd64,arm64,ppc64le,arm/v7の同時ビルドで1時間19分、s390x単独で45分です。
前述のとおり最悪ケースで6時間後に強制停止されました。

image.png

ざっと計算したところ合計13.5時間程度回しているようです。
パブリックレポジトリなので全部無料。お得感ヤバいです。
無料だから過剰に回してる感はありますが、一応キャッシュは使ってます。

cron

このスクリプトはcronで定期実行する設計になっています。
Docker内でcronを動かすのは以下の理由で望ましくないので、下の記事を参考にホスト側のcronから設定するようにしました。
そのせいでセットアップの手間が増えています。

  • docker pullなどをする度に設定が消える。
  • コンテナで動かすプロセスが増える。
  • ログ管理が面倒。

@YuukiMiyoshi(2019) 「Docker + Cron環境を実現する3つの方法」 Qiita

懸念点

1コンテナ1プロセス

Dockerは1コンテナ1プロセスが推奨されているようです(Stack Exchange)。
cgidは一応デーモンですから、これを逸脱しています。

セットアップの簡便さのためにDockerを利用している感覚なので別にどっちでもいいですし、現実問題Apacheとcgidを分けろと言われても無理ですが、マシン再起動時にcgidの起動が失敗する例を確認しました。
うっかりdocker-compose restartしてしまったのでログが消えてしまいましたが、ブラウザ側からは503 Service Unavailableが、ログにはcouldn't bind unix domain socketなどが記録されていたはずです。
1コンテナ1プロセスにしておけば発生しなかった問題のようにも見えました。タイミング的な何かでしょうか。謎です。

容量

nicochcgiのイメージは654MBにもなります。
ubuntuにffmpegをインストールすると557MB要求されるのでほとんどがffmpegです。
多少軽量化しても無駄なのが良く分かりますね。

$ sudo docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
ghcr.io/kurema/nicochcgi/nicochcgi   latest              14fdd99f2dc5        22 hours ago        654MB
ghcr.io/kurema/letmesee              latest              869e7d478ba0        4 days ago          414MB
httpd                                latest              3dd970e6b110        7 weeks ago         138MB
ubuntu                               latest              9140108b62dc        2 months ago        72.9MB
$ apt install --no-install-recommends ffmpeg
...
Need to get 90.1 MB of archives.
After this operation, 557 MB of additional disk space will be used.

正直、654MBと言えば仮想マシンみたいな数字で「これをコンテナ内で実行するのはちょっと…」という気になります。
どうせサーバーには普通にffmpeg入れますし、エンコード支援とかが関係すれば厄介そうです(今回はサムネ作成とフォーマット変換程度ですが)。
「Docker ffmpeg」で検索すれば2014年のクックパッドの記事が出たりしますから普通にアリっぽいですし、スクリプトを変更するのも嫌なので仕方ないですが、微妙な感じはします。

サイズを縮小するなら用途を絞って自前コンパイルでもするのが選択肢かもしれませんが、色々とあまりやりたくはないです。

参考記事

Docker入門にあたって参考にした記事を下に挙げます。

  • Docker
    • 武井 (2020) 「【連載】世界一わかりみが深いコンテナ & Docker入門」SIOS TECH.LAB
      • Docker全般。入門で一番役に立ちました。
    • kooohei (2015) 「Dockerコンテナの作成、起動〜停止まで」 Qiita
      • Dockerのコマンド集です。基本。
      • 最近はビルドはGitHub Actionsで、サーバー上ではdocker-composeメインで使う数種類程度は覚えました。
    • senyoltw (2015)「Docker上でApacheコンテナを作成しCGIのコンテンツを走らせるまで」ワタナベ書店
      • #1でも参照したCGIの記事です。
      • #Scriptsock cgisockのコメントインしていない点だけは注意が必要です。
    • @yohm (2019) 「dockerでvolumeをマウントしたときのファイルのowner問題」 Qiita
    • dd_fort (2020) 「Dockerのvolumeでpermission deniedが発生した場合の解決法」 ラクス エンジニアブログ
      • docker -vのownerの話はめんどくさいですね。chmodで権限を緩くすることで強引に解決してます。
      • 自分の環境ではとりあえず問題は発生していません。
    • @YuukiMiyoshi (2019) 「Docker + Cron環境を実現する3つの方法」 Qiita
      • Dockerでcronを実行するには。「ホストのcronからコンテナ内のプログラムを実行する」の方法を選択しました。
    • @homines22 (2020) 「【GitHub Actions】GitHub Package Registryを利用して同一Dockerイメージをjobで共有する」 Qiita
      • GitHub Package Registryなのでそろそろ非推奨です。
      • ただしGitHub Container Registryは現在ベータなのでまだGitHub Package Registryの方が良いのかも。
  • Apache
    • shinoda (2017)「Apache2 で、CGI ソースが表示されちゃう件の対応(Debian APT パッケージ編)」電気ウナギ的○○
      • よく読めばa2enmod cgiでcgidがロードされると書いてありますね。

おまけ

Dockerの初体験的な感じだったので「Docker未経験から2週間で」みたいなタイトルにしようかと思いましたが辞めました。
既に流行ってないみたいだし、「確かにDockerは未経験だけど…」って感じなので。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSL2環境でVSCode + Dockerで開発する為に

Windows環境で開発してます。
今年の初め、ChromeBookで開発する機会があってterminalというものにはじめて触れ、「なんて便利なものだ!」なんて思ったものです。
結局ChromeBookはOSの壁があり、ガチの開発環境には向かない事がわかってからはサブマシンになりましたが、あの感覚が忘れられず。
一瞬MACを。。。とも思いましたが、もともと使っていたWindowsが結構いいスペックだったので、それならまずはWSL2の環境を作ってみようと思った次第です。
ただ、実際にやってみると意外と言う事を聞いてくれません(笑)
この記事では備忘録としてハマった事を書いていこうと思います。

参考
Windows 10 用 Windows Subsystem for Linux のインストール ガイド
Docker Desktop WSL 2 バックエンド

Docker開発環境を移動したい

WSL2のディストリビューションは%LocalAppData%(C:\Users[ユーザー]\AppData\Local\Packages等)に保存されます。
私の場合、CドライブはSSDで200G強しかないので、このままですとすぐに枯渇してしまいます。
よってまずはこれらをDドライブに移す事をしました。

  1. Dockerを停止する

  2. PowerShellでディストリビューションを確認する

    wsl -l
    Ubuntu-20.04(規定)
    docker-desktop-data
    docker-desktop
    
  3. 対象のディストリビューションをエクスポートする
    上記のうち、docker-desktopdocker-desktop-dataがDockerのディストリビューションです。

    wsl --export docker-desktop docker-desktop.tar
    wsl --export docker-desktop-data docker-desktop-data.tar
    
  4. エクスポートしたディストリビューションを削除する

    wsl --unregister docker-desktop
    wsl --unregister docker-desktop-data
    
  5. エクスポートしたtarファイルをインポートする

    私の場合はd:\wslというフォルダを作成してそこに保存しています。

    wsl --import docker-desktop D:\wsl\docker-desktop docker-desktop.tar
    wsl --import docker-desktop-data D:\wsl\docker-desktop-data docker-desktop-data.tar
    
  6. Dockerを起動する

DockerをWSL2で動かしているのに激重い

DockerをWSL2で動かすと爆速になる。。。
そんな触れ込みを見て環境を構築している訳ですが、実際に動かすと何とも見るに堪えられないくらい重くなりました。(特にPHP等の動的に描画している部分)
この時点での私の環境は、Docker => WSL2(Cドライブ)ソースコード => Widowns(Dドライブ)という環境となっていました。
WSL2環境とWindows環境はつながっている(マウントされている)ので、もちろんWSL2上からWindow上のファイルにアクセスはできます。
しかし、WSL2(Linux)とWindowsではファイルシステムの構造が異なるので、WSL2上からWindows管理上のファイル操作をすると激重となってしまうとの事でした。
よってソースコードをWSL2上に移動しました。

尚、ソースコードをWSL2(Cドライブ)にバンバン置いていくと、やはりシステムドライブが枯渇していくので、Docker開発環境を移動したいDockerと同様にUbuntu-20.04もDドライブに移動しました。

VSCode上からdocker-compose buildできない

WSL2上でDockerを起動し、ソースコードをWSL2上に置く事でまともに動作する様になりました。
しかし、VSCodeでプロジェクトを開いてdocker-composeでbuildするとエラーが発生してbuildする事ができませんでした。

$ docker-compose up -d --build
Creating network "only-frontweb_default" with the default driver
Building web
Traceback (most recent call last):
  File "docker-compose", line 3, in <module>
  File "compose\cli\main.py", line 67, in main
  File "compose\cli\main.py", line 126, in perform_command    
  File "compose\cli\main.py", line 1070, in up
  File "compose\cli\main.py", line 1066, in up
  File "compose\project.py", line 615, in up
  File "compose\service.py", line 346, in ensure_image_exists 
  File "compose\service.py", line 1125, in build
  File "site-packages\docker\api\build.py", line 148, in build
TypeError: You must specify a directory to build in path      
[19240] Failed to execute script docker-compose

これは単純にVSCodeの使い方を間違っているだけでした。
Windows環境からWSL環境内にアクセスする場合、エクスプローラーから\\wsl$と打つ事でアクセスできます。
しかしVSCodeからフォルダを開く時に\\wsl$でWindows側から開くとWSL2で起動しているdockerが上手く動作しないようでした。
VSCodeのターミナル上から、

wsl
code .

と打つ事により、VSCodeがWSL2から起動した事になり、正常にdocker-compose buildできるようになります。

さて、VSCodeの拡張機能にはこういうものがあります。
image.png
これをインストールしておけば、
image.png
ここから直にWSL2の環境から起動する事ができます。
image.png
WSL2側では、WSL2用の拡張機能を有効化する事ができます。
image.png
これでクリックのみで完結するようになりました。

WSL2でソフトウェアのインストールができない

ここまできてやっとWindowsとWSL2(Ubuntu)は切り離して考えないとダメなんだと分かった私です。
基本的にはDockerで環境作るので基本的には不要ではありますが、それでもインストールしないとならないものが発生してきます。
その為、WSL2で

sudo apt update

を実行した所、

Err:1 http://archive.ubuntu.com/ubuntu focal InRelease

こういうエラーが大量に発生してインストールできず。
エラーの種類から調べていくと、どうやらホスト名が解決できていないという事がわかりました。
(他の構築している方の記事を拝見すると、あまり出てこないのでインストールするディストリビューションによって違うのかもしれません)

  1. /etc/resolv.confを削除する
    初期状態では、/run/resolvconf/resolve.confへのシンボリックリンクになっているので、これを削除します。
  2. /etc/resolv.confを作成する
    物理的なファイルを作成し、下記を記述します。

    nameserver 8.8.8.8
    nameserver 8.8.4.4
    
  3. WSL2を再起動する

これでインストール可能になりました。

まとめ

Windowsにはあまりないファイル(フォルダ)の権限との格闘はつづいていますが、これで一通り開発環境は整っています。
今の所WSL2 + Dockerで使用しているのはPHP環境のみなので大きな問題は発生していませんが、様々な環境で開発しているうちに問題が発生した場合は備忘録を兼ねてまた書こうと思います。


:christmas_tree: FORK Advent Calendar 2020
:arrow_left: 2日目 strapi+nuxt.jsでアドベントカレンダーを作ろう @sunnyplace
:arrow_right: 4日目 comming soon @sk2usa

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

脳死で出来る ECS (Fargate)でDocker環境のデプロイ

はじめに

前回の記事を書いていくと、今の流行はどうやらEC2ではなくECSであるそうなので、今回はそちらを勉強してみました。
備忘録として書いていますが、自分と同じくAWS初心者の方の手助けとなれば幸いです。

対象読者

  • AWSアカウント登録済み
  • EC2VPCなどの基礎的なAWSの知識は持っている
  • 自分

全体構造

ECSとは

Amazon Elastic Container Serviceの略称で、EC2インスタンス(もしくはFargate)コンテナを複数操作することができるサービスです。また、他のAWSサービスとも連携することが容易です。
簡単にECSの全体構造を表してみました。
201201 ECS.png

詳しくはまた作成するときに説明します。

一応こちらが公式です。

ECSを含めた構築の流れ

Dcoker イメージの作成

環境を揃えるため、今回使用するDocker イメージを作成します。
「自分の環境で試したいぜ」って方はこの章は無視してもらって構いません。
一応コードは、ここ に公開しています。

pullしてきたら、READMEにしたがって

docker build --tag 『今回のDockerイメージ名(任意)』 .

を実行。

docker images

REPOSITORY     TAG          IMAGE ID            CREATED              SIZE
aws-example    latest       05ad15c1ad82        About a minute ago   527MB

で、イメージが作成できているか確認できます。

ちなみに、

docker ps

CONTAINER ID        IMAGE           COMMAND                  CREATED              STATUS                      PORTS                    NAMES
817ed89f61b7        aws-example     "docker-entrypoint.s…"   32 seconds ago       Exited (0) 25 seconds ago                            gracious_galois

docker start 『上の CONTAINER ID』
docker exec -it 『上の CONTAINER ID』 sh 

でコンテナの中に入ることができます。

ECRにpush

ECRとは、Amazon Elastic Container Registryの略称で、Dockerイメージを保管できるレジストリです。
AWS版Docker Hubみたいな認識でいいです。

では早速、DockerイメージECRpushしていきます。

AWSコンソールのサービスから『ECR』を選択。
スクリーンショット 2020-12-01 17.20.50.png

リポジトリを作成』を押下。
スクリーンショット 2020-12-01 17.21.13.png

ECR上のリポジトリ名を記入。
リポジトリを作成』を押下。
スクリーンショット 2020-12-01 17.21.43.png

作成したリポジトリを押下。

プッシュコマンドの表示』を押下。
スクリーンショット 2020-12-01 17.24.55.png

以下に表示されるコマンドを順に実行。
スクリーンショット 2020-12-01 17.42.18.png


※ AWS CLIをインストールしていない方は...

AWS公式のdockerイメージをインストール

docker run --rm -it amazon/aws-cli --version

長いコマンドを aws ~~~~みたいに打てるようにエイリアスする。

alias aws='docker run --rm -ti -v ~/.aws:/root/.aws -v $(pwd):/aws amazon/aws-cli'

以上でAWS CLIがコンテナ上で起動するようになります。


ブラウザをリロードして、以下のように表示されていればECRpushすることができています。
あとで必要なので、ついでに URI もコピーしておきます。
スクリーンショット 2020-12-02 13.11.19.png

クラスターの作成

ECSメニューの『クラスター』から『今すぐ始める』を押下。
スクリーンショット 2020-12-02 13.09.56.png

customの『設定』を押下。
スクリーンショット 2020-12-02 14.12.20.png

今回のコンテナ名を決定。
イメージ』に先ほどコピーしたECRの URI をペーストする。
ポートマッピング』に今回開放している 3000番を記入。
更新』を押下。
スクリーンショット 2020-12-02 14.16.29.png

元の画面に戻ったら、『』を押下。
スクリーンショット 2020-12-02 14.16.49.png

ロードバランサーの種類』は『Application Load Balancer』を選択。
』を押下。
スクリーンショット 2020-12-02 14.22.52.png

クラスター名』で、今回のクラスターの名前を決定。
』を押下。
スクリーンショット 2020-12-02 14.24.56.png

確認して問題なければ、『作成』を押下。
スクリーンショット 2020-12-02 14.26.29.png

以下の画面が表示されれば、ECSの作成は完了です。
念のため、デプロイできているか確認します。

サービスの表示』を押下。
スクリーンショット 2020-12-02 14.33.31.png

タスク』を選択。
作成されているタスクを押下。
スクリーンショット 2020-12-02 14.34.54.png

パブリックIPをコピー。
スクリーンショット 2020-12-02 14.34.57.png

http://『パブリックIP』:3000/ でアクセスするとNuxtアプリが表示されます。

ACMでSSL証明

ACMとは、AWS Certificate Managerの略称で SSL証明書などを無料で発行できるサービスです。
こちらを用いて、今回デプロイしたアプリを SSL証明していきます。

また今回は、Route53で独自ドメインが紐づいたホストゾーンを既に作成しているものとします。
また本稿の前準備として、ロードバランサーAレコードをホストゾーンに紐づけておいてください。

AWSコンソールから、ACMもしくはCertificate Managerを選択。
スクリーンショット 2020-12-02 15.34.09.png

証明書のリクエスト』を押下。
スクリーンショット 2020-12-02 15.42.26.png

自分で作成しているホストゾーンに紐づいているドメイン名を記入。
この際サブドメインを設定しても良い。(www.ecs-example.siteみたいにしても良い)
次へ』を押下。
スクリーンショット 2020-12-02 15.42.45.png

DNSの検証』を選択。
次へ』を押下。
スクリーンショット 2020-12-02 15.42.48.png

確認』を押下。
スクリーンショット 2020-12-02 15.42.51.png

確定とリクエスト
スクリーンショット 2020-12-02 15.42.53.png

』を押下。
Route 53でレコードの作成』を押下。
出てくるポップアップの内容を確認し、『作成』を押下。
続行』を押下。
スクリーンショット 2020-12-02 15.43.01.png

5分ほど待ち『発行済み』になればOK。
スクリーンショット 2020-12-02 17.43.44.png

ドメイン設定

クラスター』の『詳細』から、『ターゲットグループ名』にあるターゲットグループを押下。
スクリーンショット 2020-12-02 14.55.08.png

ロードバランサー』を押下。
スクリーンショット 2020-12-03 14.06.56.png

説明』タグが選択されていることを確認。
下にスクロールして自分の『セキュリティグループ』を押下。
スクリーンショット 2020-12-03 14.07.46.png

ELB Allowed Ports』と記されたセキュリティグループを選択。
インバウンド』を選択。
編集』を押下。
スクリーンショット 2020-12-03 14.08.31.png

ルールの追加』を押下。
タイプ』に『HTTP』『HTTPS』を選択。(基本的に自動で『ポート番号』『ソース』が記入されますが、もしそうでない場合手動で記入してください。)
保存』を押下。
スクリーンショット 2020-12-03 14.08.51.png

左側のメニューから『ロードバランサー』を選択。
今回使用するVPCを選択。
リスナーの追加』を押下。
スクリーンショット 2020-12-03 14.30.36.png

プロコトル:ポート』を『HTTPS』『443』に選択。
アクションの追加』を押下。
転送先...』を選択。
今回使用するターゲットグループを選択。
デフォルトのSSl証明書』は『ACMから(推奨)』を選択して、先ほどACMで発行したSSL証明書を選択。
スクリーンショット 2020-12-03 14.32.11.png

上と同様にロードバランサーのメニュー画面から『リスナーの追加』を押下。
プロコトル:ポート』を『HTTPS』『80』に選択。
アクションの追加』を押下。
リダイレクト先...』を選択。
スクリーンショット 2020-12-03 14.32.36.png

参考記事

Nuxt.jsでアプリ作成→Dockerfileを使ってイメージ作成→fargateでデプロイ→ドメイン設定・SSL化まで

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECS (Fargate) + ACM + Route53 入門

はじめに

前回の記事を書いていくと、今の流行はどうやらEC2ではなくECSであるそうなので、今回はそちらを勉強してみました。
備忘録として書いていますが、自分と同じくAWS初心者の方の手助けとなれば幸いです。

対象読者

  • AWSアカウント登録済み
  • EC2VPCなどの基礎的なAWSの知識は持っている
  • 自分

全体構造

201201 ECS-Page-2.png

ECSとは

Amazon Elastic Container Serviceの略称で、EC2インスタンス(もしくはFargate)コンテナを複数操作することができるサービスです。また、他のAWSサービスとも連携することが容易です。
簡単にECSの全体構造を表してみました。
201201 ECS.png

詳しくはまた作成するときに説明します。

一応こちらが公式です。

ECSを含めた構築の流れ

Dcoker イメージの作成

環境を揃えるため、今回使用するDocker イメージを作成します。
「自分の環境で試したいぜ」って方はこの章は無視してもらって構いません。
一応コードは、ここ に公開しています。

pullしてきたら、READMEにしたがって

docker build --tag 『今回のDockerイメージ名(任意)』 .

を実行。

docker images

REPOSITORY     TAG          IMAGE ID            CREATED              SIZE
aws-example    latest       05ad15c1ad82        About a minute ago   527MB

で、イメージが作成できているか確認できます。

ちなみに、

docker ps

CONTAINER ID        IMAGE           COMMAND                  CREATED              STATUS                      PORTS                    NAMES
817ed89f61b7        aws-example     "docker-entrypoint.s…"   32 seconds ago       Exited (0) 25 seconds ago                            gracious_galois

docker start 『上の CONTAINER ID』
docker exec -it 『上の CONTAINER ID』 sh 

でコンテナの中に入ることができます。

ECRにpush

ECRとは、Amazon Elastic Container Registryの略称で、Dockerイメージを保管できるレジストリです。
AWS版Docker Hubみたいな認識でいいです。

では早速、DockerイメージECRpushしていきます。

AWSコンソールのサービスから『ECR』を選択。
スクリーンショット 2020-12-01 17.20.50.png

リポジトリを作成』を押下。
スクリーンショット 2020-12-01 17.21.13.png

ECR上のリポジトリ名を記入。
リポジトリを作成』を押下。
スクリーンショット 2020-12-01 17.21.43.png

作成したリポジトリを押下。

プッシュコマンドの表示』を押下。
スクリーンショット 2020-12-01 17.24.55.png

以下に表示されるコマンドを順に実行。
スクリーンショット 2020-12-01 17.42.18.png


※ AWS CLIをインストールしていない方は...

AWS公式のdockerイメージをインストール

docker run --rm -it amazon/aws-cli --version

長いコマンドを aws ~~~~みたいに打てるようにエイリアスする。

alias aws='docker run --rm -ti -v ~/.aws:/root/.aws -v $(pwd):/aws amazon/aws-cli'

以上でAWS CLIがコンテナ上で起動するようになります。


ブラウザをリロードして、以下のように表示されていればECRpushすることができています。
あとで必要なので、ついでに URI もコピーしておきます。
スクリーンショット 2020-12-02 13.11.19.png

クラスターの作成

ECSメニューの『クラスター』から『今すぐ始める』を押下。
スクリーンショット 2020-12-02 13.09.56.png

customの『設定』を押下。
スクリーンショット 2020-12-02 14.12.20.png

今回のコンテナ名を決定。
イメージ』に先ほどコピーしたECRの URI をペーストする。
ポートマッピング』に今回開放している 3000番を記入。
更新』を押下。
スクリーンショット 2020-12-02 14.16.29.png

元の画面に戻ったら、『』を押下。
スクリーンショット 2020-12-02 14.16.49.png

ロードバランサーの種類』は『Application Load Balancer』を選択。
』を押下。
スクリーンショット 2020-12-02 14.22.52.png

クラスター名』で、今回のクラスターの名前を決定。
』を押下。
スクリーンショット 2020-12-02 14.24.56.png

確認して問題なければ、『作成』を押下。
スクリーンショット 2020-12-02 14.26.29.png

以下の画面が表示されれば、ECSの作成は完了です。
念のため、デプロイできているか確認します。

サービスの表示』を押下。
スクリーンショット 2020-12-02 14.33.31.png

タスク』を選択。
作成されているタスクを押下。
スクリーンショット 2020-12-02 14.34.54.png

パブリックIPをコピー。
スクリーンショット 2020-12-02 14.34.57.png

http://『パブリックIP』:3000/ でアクセスするとNuxtアプリが表示されます。

ACMでSSL証明

ACMとは、AWS Certificate Managerの略称で SSL証明書などを無料で発行できるサービスです。
こちらを用いて、今回デプロイしたアプリを SSL証明していきます。

また今回は、Route53で独自ドメインが紐づいたホストゾーンを既に作成しているものとします。
また本稿の前準備として、ロードバランサーAレコードをホストゾーンに紐づけておいてください。

AWSコンソールから、ACMもしくはCertificate Managerを選択。
スクリーンショット 2020-12-02 15.34.09.png

証明書のリクエスト』を押下。
スクリーンショット 2020-12-02 15.42.26.png

自分で作成しているホストゾーンに紐づいているドメイン名を記入。
この際サブドメインを設定しても良い。(www.ecs-example.siteみたいにしても良い)
次へ』を押下。
スクリーンショット 2020-12-02 15.42.45.png

DNSの検証』を選択。
次へ』を押下。
スクリーンショット 2020-12-02 15.42.48.png

確認』を押下。
スクリーンショット 2020-12-02 15.42.51.png

確定とリクエスト
スクリーンショット 2020-12-02 15.42.53.png

』を押下。
Route 53でレコードの作成』を押下。
出てくるポップアップの内容を確認し、『作成』を押下。
続行』を押下。
スクリーンショット 2020-12-02 15.43.01.png

5分ほど待ち『発行済み』になればOK。
スクリーンショット 2020-12-02 17.43.44.png

ドメイン設定

クラスター』の『詳細』から、『ターゲットグループ名』にあるターゲットグループを押下。
スクリーンショット 2020-12-02 14.55.08.png

ロードバランサー』を押下。
スクリーンショット 2020-12-03 14.06.56.png

説明』タグが選択されていることを確認。
下にスクロールして自分の『セキュリティグループ』を押下。
スクリーンショット 2020-12-03 14.07.46.png

ELB Allowed Ports』と記されたセキュリティグループを選択。
インバウンド』を選択。
編集』を押下。
スクリーンショット 2020-12-03 14.08.31.png

ルールの追加』を押下。
タイプ』に『HTTP』『HTTPS』を選択。(基本的に自動で『ポート番号』『ソース』が記入されますが、もしそうでない場合手動で記入してください。)
保存』を押下。
スクリーンショット 2020-12-03 14.08.51.png

左側のメニューから『ロードバランサー』を選択。
今回使用するVPCを選択。
リスナーの追加』を押下。
スクリーンショット 2020-12-03 14.30.36.png

プロコトル:ポート』を『HTTPS』『443』に選択。
アクションの追加』を押下。
転送先...』を選択。
今回使用するターゲットグループを選択。
デフォルトのSSl証明書』は『ACMから(推奨)』を選択して、先ほどACMで発行したSSL証明書を選択。
スクリーンショット 2020-12-03 14.32.11.png

上と同様にロードバランサーのメニュー画面から『リスナーの追加』を押下。
プロコトル:ポート』を『HTTPS』『80』に選択。
アクションの追加』を押下。
リダイレクト先...』を選択。
スクリーンショット 2020-12-03 14.32.36.png

参考記事

Nuxt.jsでアプリ作成→Dockerfileを使ってイメージ作成→fargateでデプロイ→ドメイン設定・SSL化まで

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【コマンド集】Dockerで使用する基本的なコマンド【14選】



これからDockerを使う人のために、dockerの基本的なコマンドをまとめてみました。良ければご活用ください。



Dockerについて

「そもそもDockerって何?」

結論、構築した環境を複数人で使いまわせるツールの事です。

例えば企業などで、複数人で開発を進める時に、エンジニアごとに開発環境やバージョンが異なると、思わぬエラーに遭遇する可能性があります。

しかし、同じ開発環境でそれぞれのエンジニアが開発を進めることによって、上記のような問題は大幅に回避する事ができます。それを簡単にできるようにするのが、Dockerの役割です。

ここでは、詳しい内容は省略して、基本的なコマンドを列挙していきます。もしPCにDockerが入っていない場合は、公式サイトでインストールから初めてください。

公式サイト:https://www.docker.com/





version

まずPCにDockerがインストールされているかどうか、確認しましょう。
コマンドはこちら。

$ docker --version


正しくインストールされていれば、このような表示になります。

Docker version 19.03.13

もしここで表示が異なる場合は、まだパソコンにDockerがインストールされていない可能性が高いので、公式サイトよりインストールを行いましょう。



login

Dockerを利用するには、まずログインが必要です。コマンドはこちら。

$ docker login

このコマンドを実行すると、Dockerの公式サイトで登録したログインIDと、パスワードの入力を求められます。ログインに成功したら、下記のような表示になります。

login succeeded




images

次に docker image の一覧を取得するコマンドはこちら。

$ docker images




ps

次に コンテナ の一覧を取得するコマンドはこちら。

<!-- Up状態のコンテナ一覧を取得 -->
$ docker ps

<!-- 全てのコンテナ一覧を取得 -->
$ docker ps -a

個人的によく使うのは、2つ目のコマンドですね。UpExited など関係なく、コンテナを表示してくれます。

また、コンテナごとに詳細情報を確認する方法はこちら

<!-- ハッシュ値で指定したコンテナの詳細情報を表示 -->
$ docker inspect 113289ac4d92

こちらは grepコマンド などと組み合わせて、コンテナのCPUやMemoryを確認するのに便利です。



pull

次に DockerHub からリポジトリを取得するコマンドはこちら。

$ docker pull hello-world

上記の場合、hello-world のリポジトリを docker image として取り込みます。



push

今度は逆に DockerHub に手元の docker image をアップするコマンドです。

$ docker push example/first-repo




build

次はホストにある Dockerfiledocker image にする方法です。
コマンドはこちら。

<!-- . はカレントディレクトリを示しています。 -->

<!-- docker imageを作成(名前はnone) -->
$ docker build .

<!-- 名前を指定してdocker imageを作成 -->
$ docker build -t new-ubuntu:latest .

<!-- Dockerfile名が異なる場合のコマンド -->
$ docker build -f Dockerfile.dev .

作成された docker image は、上記で紹介した $ docker images で確認できるので、作成したら正常に作られているか確認するようにしましょう。





create

次は docker image から コンテナ を作成する方法です。
コマンドはこちら。

$ docker create hello-world

作成する際は、予め $ docker images で名前かハッシュIDを確認しておきましょう。(hello-worldの部分はハッシュIDでも可能です。)

また作成したら、正常に作成されているか、$ docker ps -a で確認しましょう。



start

次は作成した(created状態の) コンテナ を起動させる方法です。
コマンドはこちら。

<!-- ハッシュIDで指定したコンテナを起動(即Exited) -->
$ docker start 113289ac4d92

<!-- ハッシュIDで指定したコンテナを起動(継続してUp) -->
$ docker start -a 113289ac4d92


またすでに一度起動を行い、Exited状態のコンテナ を起動するコマンドはこちら。

<!-- Exited状態のコンテナを再起動する -->
$ docker restart 113289ac4d92




stop

次は Up状態のコンテナExitde状態 にする方法です。
コマンドはこちら。

$ docker stop 113289ac4d92




run

次に上記で紹介したcreateコマンドstartコマンド を、一気に実行する方法です。
コマンドはこちら。

<!-- コンテナを作成し起動(名前はnone) -->
$ docker run hello-world

<!-- コンテナを作成し起動後、bashでコマンド入力できる状態(名前はnone) -->
$ docker run -it ubuntu bash

<!-- 名前をつけてコンテナを作成し起動 -->
$ docker run --name sample ubuntu

紹介しておいてあれですが、個人的には createコマンドstartコマンド はほどんど使用しておらず、ほぼ runコマンド で済ませています。

便利なので、実務でも使用する際は、こちらがメインになると思われます。

runコマンドを理解する上で、createコマンドとstartコマンドも、知っておいた方がいいと思ったので、先に記述させていただきました。




exec

次に Up状態のコンテナ でシェル(bashなど)を実行できるようにします。
コマンドはこちら。

<!-- 起動状態のコンテナにbashで操作できるようにする -->
$ docker exec -it 113289ac4d92 bash

ちなみに、Exited状態のコンテナ の場合は、エラーになってしまうので注意が必要です。

また、コンテナの操作から元の操作に戻すには、$ exit か、「ctrl」+「p」+「q」 になります。違いは、Up状態を継続するかどうかです。(後者が継続)



rm

次に Exited状態のコンテナ を削除する方法です。
コマンドはこちら。

$ docker rm 113289ac4d92

実行したら、正常にコンテナが削除されているか、$ docker ps -a で確認しましょう。

また、一括削除をしたい場合のコマンドはこちら。

$ docker system prune

途中、本当に削除するかどうかを聞かれますので、y で削除が完了します。



rmi

次に コンテナで使用されていないdocker image を削除する方法です。
コマンドはこちら。

$ docker rmi hello-world

実行したら、正常にdocker imageが削除されているか、$ docker images で確認しましょう。



まとめ

以上が Docker を扱う上で必要な、基本的なコマンドになります。 Docker は利用している企業も多いと思いますので、習得しておいて損はないかと。

最後までご覧いただき、ありがとうございました!





筆者:yuki|学習10日目で初案件獲得→現在はフルスタックエンジニア転職に向けて学習中
Qiita:https://qiita.com/yuki4839
Twitter:https://twitter.com/yuki35522891

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

local docker 環境の説明

本記事は、サムザップ Advent Calendar 2020 #1 の 12/5 の記事です。

スマホゲームの企画・運営事業を行っているサムザップで、エンジニアをしています森本です

サムザップでは、local 環境を docker を用いて構築しているプロジェクトが多いです
その一例として、私の関わっているプロジェクトでの local 環境を紹介したいと思います
使用している docker コンテナは以下等です

  • nginx
  • php-fpm
  • MySQL
  • memcache
  • Redis
  • phpmyadmin

また、DB マイグレーションに

  • phinx migration

を使用しています

フォルダ構成

local/
 ├ application/
 │ ├ mysql/
 │ │ ├ 1_initial_mysql.sh
 │ │ └ 2_initial_mysql.sql
 │ └ web/
 │   └ initial_web.sh
 ├ docker/
 │ ├ gatling/
 │ │ └ Dockerfile
 │ ├ misc/
 │ │ └ data/
 │ │   └ DB実態
 │ ├ nginx/
 │ │ ├ 鍵回り省略
 │ │ ├ Dockerfile
 │ │ ├ fastcgi.conf
 │ │ ├ nginx.conf
 │ │ └ server.conf
 │ ├ php-fpm/
 │ │ ├ Dockerfile
 │ │ ├ php.ini
 │ │ └ www.conf
 │ └ docker-compose.yml
 ├ dump/
 │ └ 移行前のDB,テーブル定義
 ├ mysql/
 │ └ custom.cnf
 └ phpmyadmin/
   ├ conf/
   │ └ config.user.inc.php
   └ sessions/
     └ 省略

local/application/

local/application/mysql/

docker の MySQL image では /docker-entrypoint-initdb.d/ というディレクトリ内に初期化用の SQL やスクリプトを置くことで、最初に image を起動したときにデータの初期化を自動的に行う仕組みがあります

  • 1_initial_mysql.sh
cd /dump/
zcat 過去DB_table.gz | mysql -uroot -p*****
...

phinx 導入前の DB、テーブルを作成しています

  • 2_initial_mysql.sql
grant all on *.* to user1@'%' identified by '*****';
grant all on *.* to user2@'%' identified by '*****';
...
insert ...

mysql ユーザー作成、管理画面初期データ作成 しています

実行順は、ファイル名順のため、ファイル名の頭に「1_」「2_」がついています

local/application/web/

  • initial_web.sh
cd .../PhinxMigration
if [ ! -e vendor ]; then
    リンク作成(省略)
    cd .../PhinxMigration
    composer install
else
    until mysqladmin ping -h mysql --silent; do
        echo ""
        echo 'waiting for mysqld to be connectable...'
        echo ""
        sleep 3
    done
    cd .../phinx_migration/
    make migrate ENV=localhost
fi

web 起動時

初回起動の場合
(という切り分けができなかったので、composer install に実行時に作成される vender フォルダがない場合)
  cd .../PhinxMigration
  composer install 実行

初回起動ではない場合
(vender フォルダがある場合)
  mysql サーバが立ち上がるまでループ
  DB マイグレーション同期実行

を実行しています

docker 起動時(=web 起動時) に、毎回、DB マイグレーション同期実行 するようにしています

composer install 実行後、DB マイグレーション同期実行 する方法(else の削除)
は、課題として残っています

local/docker/

local/application/nginx/

  • Dockerfile
FROM nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD fastcgi.conf /etc/nginx/fastcgi.conf
ADD server.conf /etc/nginx/conf.d/default.conf
ADD localhost.key /etc/ssl/private/localhost.key
ADD localhost.crt /etc/ssl/certs/localhost.crt
ADD dhparam.pem /etc/nginx/ssl/dhparam.pem

nginx イメージに各設定ファイルをaddしています

  • fastcgi.conf
fastcgi_param   QUERY_STRING            $query_string;
fastcgi_param   REQUEST_METHOD          $request_method;
...

一般的な検索結果サイトから取得したもの

プロジェクトで使用しているものを追加しています

  • nginx.conf
user            www-data;
pid     /var/run/nginx.pid;

# coreモジュールの設定

http{
    # httpモジュールの設定
}
...

一般的な検索結果サイトから取得したもの

ソケット通信に変更しています
user www-data;
pid /var/run/nginx.pid;

  • server.conf
server {
    listen 80;
    ...
}
server {
    listen 443 ssl http2;
    ...

    location / {
        # other
        try_files $uri $uri/ /index.php?$args;
    }
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php7-fpm.sock;
        fastcgi_index index.php;
        include      fastcgi.conf;
    }

    ssl_certificate             /etc/ssl/certs/localhost.crt;
    ssl_certificate_key         /etc/ssl/private/localhost.key;
    ...
}
server {
    listen 80;
    server_name ~^(?<user>.+)\.local\.jp$;

    root       /var/www/$user/;
    ...
}

一般的な検索結果サイトから取得したもの

プロジェクトで使用している設定(記述変更、削除しています)

ソケット通信に変更しています
fastcgi_pass unix:/var/run/php7-fpm.sock;

また、ローカルに複数ソースを git clone している人のために


test.local.jp
にアクセスすると
/var/www/test/
以下のソースを見に行くようにしています

local/application/php-fpm/

  • Dockerfile
FROM php:7.4-fpm

ADD php.ini /usr/local/etc/php/conf.d/php.ini
ADD www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

RUN apt-get update && apt-get install -y libmcrypt-dev mariadb-client bc zlib1g-dev libmemcached-dev procps curl unzip git libonig-dev \
  && docker-php-ext-install pdo_mysql mysqli mbstring opcache bcmath

RUN pecl install memcached-3.1.3 \
    && docker-php-ext-enable memcached

RUN mkdir -p /usr/src/php/ext/redis \
    && curl -L https://github.com/phpredis/phpredis/archive/5.1.1.tar.gz | tar xvz -C /usr/src/php/ext/redis --strip 1 \
    && echo 'redis' >> /usr/src/php-available-exts \
    && docker-php-ext-install redis

RUN pecl install timecop-beta \
    && docker-php-ext-enable timecop

RUN pecl install apcu \
  && docker-php-ext-enable apcu

# install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

ソケット通信の設定ファイルは、「zzz-」をつける必要があります
PHPの公式DockerイメージでUNIXソケット通信しようとして罠にハマるの巻
ADD www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

  • www.conf

ソケット回りの設定等

[www]
listen = /var/run/php7-fpm.sock
listen.owner = www-data
listen.group = www-data
request_terminate_timeout = 30s

local/application/docker-compose.yml

version: '3.7'

volumes:
  php_sockert:

services:
  memcached:
    image: memcached
    container_name: memcached

  redis:
    image: redis
    container_name: redis

  mysql:
    image: mysql:5.7.31
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --max_allowed_packet=32M
    environment:
      MYSQL_ROOT_PASSWORD: ********
      MYSQL_DATABASE: mysql_database
      MYSQL_USER: user
      MYSQL_PASSWORD: ********
      TZ: "Asia/Tokyo"
    ports:
      - "3306:3306"
    volumes:
      - ../application/mysql/:/docker-entrypoint-initdb.d/
      - ../dump/:/dump/
      - ../mysql/:/etc/mysql/conf.d/
      - ./misc/data:/var/lib/mysql
    container_name: mysql

  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    environment:
      TZ: "Asia/Tokyo"
    volumes:
      - ../../local/:/var/www/local
      # local複数環境用     - ../../test/:/var/www/test
      - php_sockert:/var/run
    depends_on:
      - web
    container_name: nginx

  web:
    build: ./php-fpm
    tty: true
    privileged: true
    ulimits:
      core: -1
    volumes:
      - ../application/web/initial_web.sh:/tmp/initial_web.sh
      - ../../local/:/var/www/local
      # local複数環境用     - ../../test/:/var/www/test
      - php_sockert:/var/run
    depends_on:
      - mysql
      - memcached
      - redis
      - rabbitmq
    environment:
      DATABASE_HOST: 'mysql'
      DATABASE_NAME: 'mysql_database'
      DATABASE_USER: 'user'
      DATABASE_PASSWORD: '********'
      TZ: "Asia/Tokyo"
    command: bash -c "bash /tmp/initial_web.sh && php-fpm"
    container_name: web

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=********
    links:
      - mysql
    ports:
      - 8080:80
    volumes:
      - ../phpmyadmin/sessions:/sessions
      - ../phpmyadmin/conf/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php

image: memcached
docker hub から落としてくるイメージです

image: mysql:5.7.31
等「:」でバージョン指定可能です

nginx:
  build: ./nginx
のように記述すると、./nginx/Dockerfile を読みにいきます
Dockerfile 内で image を指定したり
command を指定したりしています

container_name: memcached
etc/hosts に上記名称が追加されます

volumes:
  - ./misc/data:/var/lib/mysql
ホストの /misc/data 以下と
ゲストの /var/lib/mysql 以下が共有されます

mysql 上記データをホストとゲストを共有していないと、docker の mysql を落とすとデータが削除されます

volumes:
 php_sockert:

 nginx:
  volumes:
   - php_sockert:/var/run

 web:
  volumes:
   - php_sockert:/var/run
 
nginx と web の /var/run が共有されます
ソケットで使用しています

command:
 イメージが作られた時点で、記述内容が実行されます
ports:
 ホストのポートとゲストのポートが結びつきます

nginx:depends_on:
  - web
nginx コンテナからwebコンテナへ通信するため web 起動してから、nginx 起動するように設定

最後に

私の関わっているプロジェクトでの local 環境を紹介いたしました

今後は

  • 本文中に記述した問題の改修
  • jenkins 導入

などを改修、導入できればと思っています!!

明日は @shirai_suguru です。お楽しみに!!

サムザップのアドベントカレンダーは人数の関係上2つあります。
こちらもよろしくお願いいたします!
サムザップ #2 Advent Calendar 2020 - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

13年ぶりにwindowsで Web開発環境を構築したメモ

Ateam Group Manager & Specialist Advent Calendar 2020の24日目はエイチーム ライフスタイルサポート事業本部でエンジニア、デザイナーのマネジメントをしている @kopug が担当します。

13年前からmacばかり使っており、まったくwindowsとはご無沙汰だったのですが、最近のwindows環境に疎くなってしまったのもあり、この度windows環境 (Thinkpad)に変えてWeb開発環境を構築をしたので、備忘録も兼ねて残していきます。

WSL2 + Ubuntuのインストール

windows環境に変えた!と言いつつも、開発環境としては Linux を使いたいので、WSL2とUbuntuのインストールをしていきます。
…といっても、オフィシャルドキュメントで構築方法が丁寧に書かれているので、以下の手順通りにやれば簡単にインストールができます。

Windows Subsystem for Linux (WSL) を Windows 10 にインストールする | Microsoft Docs

Linux 用 Windows サブシステムには 2 つの異なるバージョンがあり、インストール プロセス中にどちらかを選択します。 全体的なパフォーマンスは WSL 2 の方が優れている ...

Windows Terminal

昔は Cygwin を入れて、Tera Term or Putty で操作していたなぁ…と思い出しながらターミナルは何がいいのかを探していたら、上で紹介したドキュメントにもあるように、Windows Terminal をインストールしました。

Windows Terminal を入手 - Microsoft Store ja-JP

Windows ターミナルは、コマンド プロンプト、PowerShell、WSL などのコマンドライン ツールおよびシェルのユーザーのための、高速、効率的、強力な、生産性を向上させる最新のターミナル アプリケーション...

image.png

設定のショートカットが「Ctrl + ,」で呼び出せて、中身は JSON なんですね。
私は以下の設定のみしてます。

Windows Terminal を立ち上げたら Default で Ubuntu になるようにする

"profiles": { "list": [] } に Ubuntu の GUID があるので、それをdefaultProfileにセットしてあげればOKです。


{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "defaultProfile": "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx}",

// 省略

FontをRictyに変更 + 背景色を透明に

この辺は好みの問題なので、好きなように。
RictyDiminished-for-Powerline から フォントをダウンロードし、インストールをします。
次に fontFace, fontSize を以下のように指定しています。

背景色を透明にする場合は、useAcrylicを有効(true)にし、acrylicOpacityで透明度を 0-1 で指定します。

それ以外にも色々と設定ができるので、詳細は以下を参考にしてください。
Windows Terminal Profile Settings | Microsoft Docs

    "profiles":
    {
        "defaults":
        {
            // Put settings here that you want to apply to all profiles.
            "fontFace": "Ricty Diminished",
            "fontSize": 14,
            "useAcrylic": true,
            "acrylicOpacity": 0.8
        },
        "list":
        [

shellをbashからzshに変更

パッケージを最新版にしてから、zsh をインストール

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install zsh

インストールが終わったら chsh で zsh に変更します。

$ chsh -s $(which zsh)

あとは自分のお好みで、.zshrc .zprofile 等に変えていってください。
私は今回 zplug から prezto にこのタイミングで変えました。

Docker

Dockerのインストール及び設定に関しても、Microsoftの公式ドキュメントに丁寧に書かれております。
こういう情報がMicrosoftから手に入る時代になったことに個人的には感動しました。笑
VS Code を使用したリモートコンテナーでの開発に関してもこちらのドキュメントに書かれているので、VS Code を使われている方はそちらをご覧ください。

Windows Subsystem for Linux で Docker コンテナーの使用を開始する | Microsoft Docs

このステップバイステップガイドでは、WSL 2 (windows Subsystem for Linux、バージョン 2) を使用して Docker Desktop For Windows をセットアップすることで、リモートコンテナーを使用した開発を始めることができます。

Vimの設定

最近 .vimrc は自分で管理しておらず、 Vim Bootstrap のお世話になっております。

Vim Bootstrap

A generator which provides a simple method of generating a .vimrc configuration for vim

上記にアクセスをして、vimで使っている言語を選択をし、カラー設定等を選んで、Generate! ボタンを押してそれをvimに食わせてもいいのですが、私は以下の方法でセットアップしております。

$ curl 'http://vim-bootstrap.com/generate.vim' --data 'langs=javascript&langs=php&langs=html&langs=ruby&langs=go&editor=vim' > ~/.vimrc
$ vim +PlugInstall +qall

詳しくはこちらをご覧ください。

最後に

最近業務で開発はする機会は少なくなっておりますが、PC組んだり環境構築をするのは趣味なのかもしれません。笑
今回久々にwindowsを触っておりますが、完全に浦島太郎状態でした。今回書いてはいませんが、windowsにもwingetという brew みたいなパッケージ管理もありこの変化にキャッチアップをしてしばらく楽しめそうです。

Ateam Group Manager & Specialist Advent Calendar 2020のラストは @zwirky がお送りします!バトンを託します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker自分用

ビルド

docker build -t stb-xxx .

起動

docker run -it --rm -p 指定のポート:5000 --name stb-xxx stb-xxx:latest
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerコマンド

イメージのダウンロード(docke hubからのダウンロード)

$ docker image pull centos: 7

イメージ の 一覧 表示

$ docker image ls

イメージ の 削除

$ docker image rm nginx

コンテナ 生成/ 起動

$ docker container run --name webserver -d -p 80:80 nginx

※-d は バックグランドでの起動

コンテナ 起動

$ docker start webserver

コンテナ 停止

$ docker stop webserver

稼働 コンテナ の 一覧 表示

$ docker container ls

コンテナを停止中のものも含めた一覧

docker ps --all

コンテナ の 稼働 確認

$ docker container stats webserver

コンテナ の 削除

$ docker container rm webserver

引用
WINGSプロジェクト阿佐 志保. プログラマのためのDocker教科書 第2版
コンテナのライフサイクルと基本コマンド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swagger-ui,swagger-editor環境構築(AWS/Docker)

前提条件

  • AWS EC2インスタンス作成ができている
  • EC2インスタンスにnginxインストール済

EC2にDockerをインストール

EC2インスタンスにSSH接続後、下記コマンドを入力

sudo amazon-linux-extras install docker

Docker起動

sudo systemctl start docker

Docker起動確認

sudo systemctl status docker

スクリーンショット 2020-12-01 12.31.24.png

ActiveになっていればOK

swagger-editorのイメージをpull

sudo docker pull swaggerapi/swagger-editor

swagger-uiのイメージをpull

sudo docker pull swaggerapi/swagger-ui

swagger-editorを起動

sudo docker run -d -p 8000:8080 swaggerapi/swagger-editor

swagger-uiを起動

sudo docker run -d -p 8001:8080 swaggerapi/swagger-ui

コンテナ起動確認

sudo docker ps

スクリーンショット 2020-12-01 12.40.12.png
swagger-editorswagger-uiが表示されればOK

nginx.confに追記

nginx.confを開く

sudo vim /etc/nginx/nginx.conf

下記内容をnginx.confに追記
server{}内に必ず追記するようにしてください。

        location /swagger-editor/docker/ {
          proxy_pass http://localhost:8000/;
          proxy_redirect off;
        }

        location /swagger-ui/docker/ {
          proxy_pass http://localhost:8001/;
          proxy_redirect off;
        }

nginxを再起動or再読み込み

再起動

sudo systemctl restart nginx

再読み込み

sudo systemctl reload nginx

すでに、docker上で動かしているコンテナがswagger系以外にもあるなら、再読み込みがおすすめ
再起動して失敗してしまうと、正常に動いていた機能も含めて全て利用不可になる可能性があるので。

URLにアクセスして、swagger-editorの表示を確認

http://(IPv4アドレス)/swagger-editor/docker/

下記のサイトが表示されればOK
スクリーンショット 2020-11-30 22.16.42.png

URLにアクセスして、swagger-uiの表示を確認

http://(IPv4アドレス)/swagger-ui/docker/

下記のサイトが表示されればOK
スクリーンショット 2020-11-30 22.18.46.png

自動起動設定(nginx,docker編)

sudo systemctl enable nginx
sudo systemctl enable docker

自動起動設定(dockerコンテナ編)

コンテナ単体の自動起動設定をすることもできます。
コンテナ起動後に、下記コマンドを入力してください。

sudo docker update --restart=always コンテナ名

自動起動設定を無効化する場合は

sudo docker update --restart=no コンテナ名

最後に

以上が、AWS/EC2とDockerを使ったswagger-ui,swagger-editorの環境構築手順です。
不明点、質問等があればコメント欄にお願い致します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでschedule:runしたときのoutputがdockerで標準出力されない問題を解消

はじめに(何が問題か)

以下のようなコマンドがあって、

app/Console/Commands/Hello.php
    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->output->writeLn('Hello !!');
        return 0;
    }

下記のように、スケジュール登録していたとして、

app/Console/Kernel.php
    /**
     * Define the application's command schedule.
     *
     * @param Schedule $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('app:hello')->everyMinute();
    }

artisan schedule:runしても

$ docker-compose run --rm task-scheduler
Creating bizany-api_task-scheduler_run ... done
Running scheduled command: '/usr/local/bin/php' 'artisan' app:hello >> '/dev/null' 2>&1

Hello !!は、標準出力されないんですね。
これを解消したという記事になります。

appendOutputTo('/dev/stdout')は効かない

\Illuminate\Console\Scheduling\EventにはappendOutputToというメソッドがあって、出力先を指定することができます。
指定しないと/dev/nullになってしまいます。
とはいえ、結論として、appendOutputTo('/dev/stdout')は効きません。

app/Console/Kernel.php
    /**
     * Define the application's command schedule.
     *
     * @param Schedule $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('app:hello')->appendOutputTo('/dev/stdout')->everyMinute();
    }

どうやって解消したか

答えは、stackoverflowにありました。

https://stackoverflow.com/questions/46586916/how-can-i-redirect-output-to-stdout-with-laravel-scheduler-command/46767123

Dockerfileにて、

RUN ln -sf /proc/1/fd/1 /var/log/laravel-scheduler.log

とした上で、出力先を/var/log/laravel-scheduler.logとします。

app/Console/Kernel.php
    /**
     * Define the application's command schedule.
     *
     * @param Schedule $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('app:hello')->appendOutputTo('/var/log/laravel-scheduler.log')->everyMinute();
    }

すると、Hello !!が標準出力されるようになります。
(Dockerコンテナのリビルドをお忘れなく)

$ docker-compose run --rm task-scheduler
Creating bizany-api_task-scheduler_run ... done
Running scheduled command: '/usr/local/bin/php' 'artisan' app:hello >> '/var/log/laravel-scheduler.log' 2>&1
Hello !!

おわりに

Laravelに限った話ではないのかもしれませんが、とにかく何も出力されなくて困り果てていたので解決できて良かったです。
同じ状況で困っている方のお役に立てればと思います。

余談

世の中、アドベントカレンダーの季節ですが、普通に記事書いてしまいました :grinning:。。
まあ、今後も書きたいときに書いていきます。

ではでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerで始めるDjango生活(6日目)

初めに

6日目です。
5日目はこちら
一旦今日が最終回になります!
今まで読んでいただきありがとうございました!

目次

  1. ビューを作成する
  2. ルーティングを行う
  3. ビューのテンプレートを作成する
  4. 画面の表示を確認する。
  5. 最後に

1. ビューを作成する

まずは、開発ディレクトリに遷移します。

# cd /root/projects/myproject/myproject/blog
# pwd
/root/projects/myproject/myproject/blog
# ls
__init__.py  __pycache__  admin.py  apps.py  migrations  models.py  tests.py  views.py
#

ビューはblogディレクトリ内のviews.pyに書きます。
このビューは投稿を取得します。

views.py(修正前)
from django.shortcuts import render

# Create your views here.
views.py(修正後)
from django.shortcuts import render, get_object_or_404
from .models import Post
# Create your views here.
def post_list(request):
    posts = Post.published.all() #5日目の最後で作成したpublishedを使用(publishedのみを取得する)
    return render(request, 
                 'blog/post/list.html', 
                 {'posts': posts})

次に投稿1つ1つを表示するビューを作成します。
views.pyに下記を追記します。

views.py
def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post, slug=post,
                                   status='published',
                                   publish__year=year,
                                   publish__month=month,
                                   publish__day=day)
    return render(request,
                  'blog/post/detail.html',
                  {'post': post})

次に今作成したビューを表示するためにルーティングを行います。

2. ルーティングを行う

ルーティングを行うためには
まずblogディレクトリにurls.pyを作成します。

urls.py
from django.urls import path
from . import views

app_name = 'blog'
urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('<int:year>/<int:month>/<int:day>/<slug:post>/',
         views.post_detail,
         name='post_detail'),
]

次にmyprojectのurls.pyを修正します。

urls.py(修正前)
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
urls.py(修正後)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls', namespace='blog')),
]

1つ1つの投稿にリンクを作成するためにblogのmodels.pyを修正します。

models.py
from django.urls import reverse
class Post(models.Model):
    # ...
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                       args=[self.publish.year,
                             self.publish.month,
                             self.publish.day, self.slug])

3. ビューのテンプレートを作成する

まずは、下記の様にディレクトリとファイルを作成してください。

# pwd
/root/projects/myproject/myproject/blog
# tree templates
templates
`-- blog
    |-- base.html
    `-- post
        |-- detail.html
        `-- list.html

2 directories, 3 files
#

ファイルには次の内容を書き込んでください。

base.html
{% load static %}
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}{% endblock %}</title>
        <link href="{% static "css/blog.css" %}" rel="stylesheet">
    </head>
    <body>
        <div id="content">
            {% block content %}
            {% endblock %}
        </div>
        <div id="sidebar">
            <h2>My Blog</h2>
            <p>This is my blog.</p>
        </div>
    </body>
</html>
list.html
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
  <h1>My Blog</h1>
  {% for post in posts %}
    <h2>
      <a href="{{ post.get_absolute_url }}">
        {{ post.title }}
      </a>
    </h2>
    <p class="date">
      Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|truncatewords:30|linebreaks }}
  {% endfor %}
{% endblock %}
detail.html
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
  <h1>{{ post.title }}</h1>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|linebreaks }}
{% endblock %}

次にbase.htmlで読み込む[css/blog.css]を作成します。
blogディレクトリに/static/css/blog.cssを作成します。

blog.css
body { 
    margin:0;
    padding:0;
    font-family:helvetica, sans-serif; 
}

a { 
    color:#00abff;
    text-decoration:none; 
}

h1 { 
    font-weight:normal;
    border-bottom:1px solid #bbb;
    padding:0 0 10px 0;
}

h2 {
    font-weight:normal;
    margin:30px 0 0;
}

#content { 
    float:left;
    width:60%;
    padding:0 0 0 30px; 
}

#sidebar { 
    float:right;
    width:30%;
    padding:10px;
    background:#efefef; 
    height:100%;
}

p.date { 
    color:#ccc;
    font-family: georgia, serif;
    font-size: 12px;
    font-style: italic; 
}

/* pagination */
.pagination { 
    margin:40px 0; 
    font-weight:bold;
}

/* forms */
label { 
    float:left;
    clear:both;
    color:#333;
    margin-bottom:4px; 
}
input, textarea { 
    clear:both;
    float:left;
    margin:0 0 10px;
    background:#ededed;
    border:0;
    padding:6px 10px;
    font-size:12px;
}
input[type=submit] {
    font-weight:bold;
    background:#00abff;
    color:#fff;
    padding:10px 20px;
    font-size:14px;
    text-transform:uppercase; 
}
.errorlist { 
    color:#cc0033;
    float:left;
    clear:both;
    padding-left:10px; 
}

/* comments */
.comment {
    padding:10px;
}
.comment:nth-child(even) {
    background:#efefef;
}
.comment .info {
    font-weight:bold;
    font-size:12px;
    color:#666;
}

ここまで完了するとblogディレクトリは下のようなファイルの配置になっています。

# tree blog
blog
|-- __init__.py
|-- __pycache__
|   |-- __init__.cpython-38.pyc
|   |-- admin.cpython-38.pyc
|   |-- apps.cpython-38.pyc
|   |-- models.cpython-38.pyc
|   |-- urls.cpython-38.pyc
|   `-- views.cpython-38.pyc
|-- admin.py
|-- apps.py
|-- migrations
|   |-- 0001_initial.py
|   |-- __init__.py
|   `-- __pycache__
|       |-- 0001_initial.cpython-38.pyc
|       `-- __init__.cpython-38.pyc
|-- models.py
|-- static
|   `-- css
|       `-- blog.css
|-- templates
|   `-- blog
|       |-- base.html
|       `-- post
|           |-- detail.html
|           `-- list.html
|-- tests.py
|-- urls.py
`-- views.py

8 directories, 21 files
#

4. 画面の表示を確認する。

ここで
127.0.0.1:8000/blog/
を確認します。
すると下のような画面になっているかと思います。
image.png
ここで投稿した物が表示されていないのは全て投稿の状態がdraftになっているためです。
ですのでテストで投稿していた一つをpublishedにします。
まずは、管理サイトに遷移します。
127.0.0.1:8000/admin/blog/post/
image.png
New titleを選択してstatusをpublishedに変更して保存します。
image.png
再度127.0.0.1:8000/blogに遷移すると下の様にpublishedにした方が表示されていると思います。
image.png
また、青いNew titleをクリックすると詳細画面に遷移します。
image.png

5. 最後に

この6日間でモデルを作成してテーブルを作り管理サイトを作り画面表示まで行いました。
djangoにはもっともっと奥深く色々なことができますので色々調べてみてください!
始めてこういった連載記事を投稿して拙い部分が多々ありましたが読んでいただきありがとうございました!
この記事では下記の本を参考に書かせていただいています。
この記事ではDjangoを実際に触って動かすことをメインに書いていますので上記のマイグレーションの詳しい説明などはこの本を読んでただければと思います。
Django 3 By Example - Third Edition

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeでnginxを立てる際に環境変数を使う方法

docker-composeでnginxを立てる際に、confファイルに環境変数を使う方法について解説します。nginxのversionが1.19より前と後でやり方が異なるので注意してください。

nginx:1.19以降

1.19以降では公式にサポートされている方法を使うことができます。
以下のようなdocker-compose.ymlを例に用います。

docker-compose.yml
version: "3"
services:
  nginx_service:
    container_name: nginx
    image: nginx:1.19-alpine
    volumes:
      - ./templates:/etc/nginx/templates
    environment:
      - PORT=8080
    ports:
      - 3000:8080

デフォルトではコンテナ内の/etc/nginx/templates/*.templateが読み込まれ、環境変数をセットした結果が、/etc/nginx/conf.dに吐き出されます。

例えば、default.conf.templateを以下のように用意します。

templates/default.conf.template
server {
  server_name         localhost;
  listen              ${PORT};
}

docker-compose up --buildを実行すると、上記ファイルがコンテナ内の/etc/nginx/templatesにマウントされます。

コンテナ内の/etc/nginx/conf.dを確認すると、以下のように環境変数がセットされたファイルが出力されていることが確認できます。

/etc/nginx/conf.d/default.conf
server {
  server_name         localhost;
  listen              8080;
}

nginx:1.19以前

1.19以前ではenvsubstコマンドを使用して、自前で環境変数をセットする必要があります。
以下のようなdocker-compose.ymlを例に用います。上記との違いは、commandでinit.shを実行していることです。

version: "3"
services:
  nginx_service:
    container_name: nginx_1.19_earlier
    image: nginx:1.18-alpine
    volumes:
      - ./nginx:/etc/nginx/conf.d
    environment:
      - PORT=8080
    ports:
      - 3000:8080
    command: sh /etc/nginx/conf.d/init.sh

default.conf.templateを以下のようにします。これは上記と変わりません。

templates/default.conf.template
server {
  server_name         localhost;
  listen              ${PORT};
}

init.shを以下のようにします。
envsubstコマンドでdefault.conf.templateに環境変数をセットして、結果をdefault.confに出力します。nginxを立ち上げるコマンドも忘れずに記載します。

templates/init.sh
#!/bin/sh

envsubst '$$PORT' < \
  /etc/nginx/conf.d/default.conf.template > \
  /etc/nginx/conf.d/default.conf

nginx -g 'daemon off;'

docker-compose up --buildを実行すると、コンテナ内の/etc/nginx/conf.dで環境変数がセットされたファイルを確認できます。

/etc/nginx/conf.d/default.conf
server {
  server_name         localhost;
  listen              8080;
}

説明に使用したファイルは以下のレポジトリを参照してください。
https://github.com/happyfukumoto/nginx_on_dockercompose

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel × Docker AlpineでER図を自動生成する

こんにちは。むらってぃーです。
Laravel Advent Calendar 2020の3日目を担当させていただきます。

皆さんはER図使っていますか?
エンティティ同士の関係が一目でわかるドキュメントであるため、手元にあれば非常に便利なものとして効力を発揮します。
一方で、DBスキーマを更新するたびにER図を書き換える必要があり、メンテナンスコストが少々高めです。

今回はLaravelでアプリケーションを開発する際、Eloquent ModelからER図を自動生成するツールを紹介します。
DockerのAlpineイメージでLaravelを動かし、その上で生成します。
そのため、チームで運用する際にも導入しやすいです。

なお、今回生成するER図はこちらです。
スクリーンショット 2020-11-22 15.56.19.png

Laravel ER Diagram Generator

https://github.com/beyondcode/laravel-er-diagram-generator

LaravelのEloquentModelから、ER図を自動生成するライブラリです。
Star数は1000超えで、多くの方に利用されているみたいです。

インストール

Dockerfile

Laravelを動かしているAlpineイメージに下記を追加します。
内部でgraphvizというツールを使っているため、apk経由でそちらをインストールします。
フォントもインストールしないと、ER図に日本語や英語が出力されないので入れておきます。

RUN apk --no-cache add graphviz fontconfig \
    && rm -rf /var/cache/apk/* \
    # graphviz用フォントインストール
    && curl -O https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip \
    && mkdir -p /usr/share/fonts/NotoSansCJKjp \
    && unzip NotoSansCJKjp-hinted.zip -d /usr/share/fonts/NotoSansCJKjp/ \
    && rm NotoSansCJKjp-hinted.zip \
    && fc-cache -fv

composer.json

Alpineイメージに入り込み、下記コマンドでLaravel ER Diagram Generatorを入れます。

composer require beyondcode/laravel-er-diagram-generator --dev

ServiceProvider

ローカルでのみこのライブラリが読み込まれるようにします。

AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        if ($this->app->environment('local')) {
            // ER図生成用のProviderはcomposer の require-devでインストールしているので、localでのみ使用
            $this->app->register(\BeyondCode\ErdGenerator\ErdGeneratorServiceProvider::class);
        }
    }
    ...

config用意・修正

下記コマンドで、configファイルをライブラリ内からプロジェクト内にコピー。

cp ./vendor/beyondcode/laravel-er-diagram-generator/config/config.php config/erd-generator.php

今回は App/Models配下にモデルファイルを置くため、configでModelファイルの読み込み先を下記に変更します。

config/erd-generator.php
<?php

return [

    /*
     * All models in these directories will be scanned for ER diagram generation.
     * By default, the `app` directory will be scanned recursively for models.
     */
    'directories' => [
        base_path('app') . '/Models',
    ],
...

Model用意

では、Modelを用意します。

Model概要

今回は下記のモデルにしてみます。

Book(本)

カラム名 備考
ID 自動採番ID
Title タイトル
AuthorID 著者ID. 著者テーブルへのリレーション.

Author(著者)

カラム名 備考
ID 自動採番ID
Name 著者名

マイグレーションファイル用意

authorsテーブルとbooksテーブルを作ります。

2020_11_09_154434_create_tables.php
...
class CreateTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('authors', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('name');
            $table->timestamps();
        });

        Schema::create('books', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->integer('author_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
        Schema::dropIfExists('authors');
    }
}

こちらはそのまま流します。

php artisan migrate

Modelファイル用意

app/Models/Author.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Author extends Model
{
}
app/Models/Book.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Book extends Model
{
    public function author(): BelongsTo
    {
        return $this->belongsTo('App\Models\Author');
    }
}

Bookには、Authorへのリレーションを記載します。

なお、ER図に記載されるその他のフィールドは、アプリケーションが繋がっているDBから読み取ってくれる動きです。

ER図生成

Alpineコンテナの中に入り、下記のコマンドを打ちます。

php artisan generate:erd er-diagram.png

すると、プロジェクトルートに er-diagram.pngというファイルが出来上がります。

スクリーンショット 2020-11-22 15.56.19.png

Userは、Laravelで最初から組み込まれているものです。
このようにBookとAuthorの関係がER図として出力されています。

BelongsToの矢印が色付けされているのが個人的には嬉しいです。

最後に

今回はLaravel ER Diagram Generatorを使い、Larvel × Docker AlpineでER図を自動生成する方法を紹介しました。
一度このように土台を作れば、migrationファイルとModelファイルの管理のみでER図を自動生成できます。
メンテナンスコストがほとんどかからないのが嬉しいですね。
ER図生成のコマンドはCIに組み込むなりして、常に最新に保つことができればさらに幸せになれそうですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker(docker-compose) + MySQL でのパスワード入力時のエラー / ERROR 1045 (28000): Access denied for user

RailsをAPIモードで Docker + MySQL を使用してWebアプリ開発をしています。

環境構築時に、
docker-compose up して dbにログインしようとすると
ERROR 1045 (28000): Access denied for user
ログインができないエラーに遭遇したので、私が解決できた方法を記載しておきます!

解決法

私の場合は、docker-compose.ymlで

docker-compose.yml
db-data:/var/lib/mysql:cached

このようにしてdbデータをローカルに保存してマウントしていたのですが、
ここに試しに作っていた、いくつかのvolumeが残っていたため、
そちらの環境変数が優先され、現在のパスワードが反映されていないという問題でした。

MySQL公式に

既にデータベースを格納しているデータディレクトリとともにコンテナを起動した場合は、下記の変数はどれも影響を及ぼさないことに留意してください。

とあるようです。
詳しくは こちら の記事をご覧ください。

つまり、
マウント先のローカルのdbを一旦削除して、再度作り直せばOKということです。

手順

docker-compose down
docker volume ls
docker volume rm (lsで表示されたvolume名をスペース区切りで並べる)
docker-compose up

で、作り直して再度ログインすると、問題なくログインできました!!

上記の手順で、
docker volume rm時にもしかすると、削除できない場合があるかもしれません。
その時は、

docker system prune
docker volume rm ○○

とするとうまくいくかも知れません!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアルやった後くらいに学んだ方が良いことをUdemyで学ぶ

はじめに

自分は去年から未経験でアプリ開発の現場に就職しました。
当時のレベルはRailsチュートリアルを3周くらいして、まあ多少はRailsのコードは書けるのかな?程度のレベルでしたが、もちろん実務ではそんなものでは全然足りずUdemyで色々受講したりして勉強しました。

そこで自分が受講して実際に役にたっている講座と内容を紹介したいと思います。
良い講座ばかりですが、注意点も多少あるので各章の最後に書いておきます。

Docker

開発環境を整えるのにもはやDockerは必須ですよね。
Dockerという言葉は聞いたことあるけど、よく分かってない。そんな声がちらほら。

私も環境構築に使うんでしょ?くらいの認識でした。
ということで下記講座を受けました。

ゼロからはじめる Dockerによるアプリケーション実行環境構築

Dockerとは何かから、実際にコンテナを立てたり、docker-composeでRails環境作るところまで分かりやすく解説されています。

※注意点
最後にSwarmの解説ありますが、今のデファクトスタンダードはKubernetes、略してk8s(かっこいい)なので、ここは特に見なくても良いのかなって気がしました。(もちろん勉強になると思います)

React

Dockerで環境構築できたらRailsはAPIモードにしてバックエンド専用、フロントはモダンなやり方で実装したいですね。そこでReactやります。

Railsチュートリアル終わったくらいだと、おそらくjQueryは分かると思いますし、RailsのViewをSlimで書いたりしていたと思いますが、バックエンドとフロントエンドを分離して、RailsはAPIでデータを返すだけ、フロントでの描画はReactで書きましょう。

Reactは二種類の書き方があり、一つはクラスコンポーネント、もう一つは関数コンポーネントです。
公式ではクラスコンポーネントはサポートを廃止する予定はないものの、新しいプロジェクトでは関数コンポーネントで書いても良いんじゃないかと言っていますので、おそらく関数コンポーネント推しなのではないでしょうか。(関数コンポーネントを絶対に使えとは書いてなかったです)

Reactのv16.8からHooksというものが導入されて、関数コンポーネントでstateを扱えるようになりました。最近は関数コンポーネントでしかコード書いてません。というか関数コンポーネントからしか勉強してないので関数コンポーネントしか書けません笑

以下動画ではHooksを使った関数コンポーネントの使い方が学べます。

【はむ式】React Hooks 入門 - HooksとReduxを組み合わせて最新のフロントエンド状態管理手法を習得

※注意点
クラスコンポーネントの解説は基本的にありません。クラスコンポーネント使わなくてもコーディングできますが、一応なんとなく読める程度にはなっておいた方が良いとは思いますので別で勉強必要かと思います。
個人的に普段書くことはありませんが、クラスコンポーネントは多少読める程度には勉強しました。

GraphQL

RailsチュートリアルでREST APIがどんなものかはなんとなく理解したかと思います。
実際は、APIとか意識してなかったと思いますが、routes.rbでURLとアクション紐付けてますよね。あれがREST APIです。
そうしたら次は別のAPIプロトコル学んでみましょう。

簡単に説明しますと例えば、REST APIでは/usersのようなurlにアクセスすると全てのuserの全ての情報が取れます。でも場合によっては全userの名前だけ欲しい時がありますよね?
そうすると、全userのidとかアバター画像のurlとか無駄なデータもフロント側に渡されます。

GraphQLでは欲しいものだけリクエストができるので、全userの名前だけ取得するといったことが可能です。

さらにスキーマ自体がAPIの仕様書になるので開発スピードが上がるということです(正直この辺は時と場合にもよるので絶対正しい訳ではないと思います)

ものすごくざっくり言うとREST APIでは、あるurlに対して何を渡すと何が返ってくるかという仕様書を作る必要があったのですが、GraphQLでは何を渡すと何が返ってくるかという仕様書を作るそれ自体がAPIの実装になるのでわざわざ別で作る必要がないといった感じです。

【はむ式】フロントエンドエンジニアのためのGraphQL with React 入門

※注意点
この講座ではGraphQLとはどういうものか学べますが、実際にAPIをどのように実装するかはカバーしていません。Railsで実装する場合にはgraphql-rubyというgemを使って実装していきます。

まとめ

これでDockerで環境構築して、RailsでGraphQLのAPI作成して、フロントをReactで作れるようになりました。
ReactでGraphQL扱うのにGraphQLクライアント必要なのですが、Apollo Clientが一番有名だと思うので、色々参考にしながら実装してみて下さい。

ちなみにReactの動画でReduxも多少学ぶことができるのですが、普段Redux使っていません。
なぜかというと前にReduxを使おうとしたらApollo Clientと競合したためです。

Apollo Client自体にstateを管理する機能があったり、useContextというHooksを使えば余程複雑ではない限りRedux使わなくても十分対応できると思います。

ということでRailsチュートリアル終わったらこの辺クリアしていくと良い感じに順番に技術を学べるんじゃないかというちょっとした経験談でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker-ComposeでDockerのベース環境を簡単構築![Sample:MySQL]

目的

docker-composeによって、簡単にDocker環境構築をしてみよう、と言う記事です。

自分が環境を先日作った際に、あまりyml文内の意味やオプション、コマンドって結局どういう意味やねん、、、とプチパニックになったのでまとめ直したものになります。そのため知識、かつ環境の構築がこの記事のみで完結できるような構成を目指しました。

あまりMarkDown記法になれていないこともあるため、少し拙い文章になるかと思いますが、読んでいただけると幸いです。

サクッと作るぞ!チートシート

この記事をみてサクッと環境作りたいわ、って人のために
まず実行するためだけの手順を完全に説明を省いて紹介していきます。
今回はサンプルのためにMySQL環境を作成しますが、
柔軟に変更できるようにコメントにて、可変できる部分は特記しています。

構成

構成は以下の通りに作成しました。

├── docker
│   └── db
│       ├── data
│       ├── mysql-config.cnf
│       └── init
│           ├── 001-create-tables.sql
│           └── init-database.sh
└── docker-compose.yml

ソースコード

1. docker-compose.yml

アプリケーションを構成するサービスをdocker-compose.ymlに書きます。

今回はMySQLにあたりますね。

これを使用するコードディレクトリの一番親のディレクトリに生成します。

# docker-compose.yml
version: '3'
services:
  # (例1)ここに使用するサービスを書きます
  mysql:
    # image > コンテナ実行時の元になるイメージです。使うサービスのリポジトリ名だったりを書きます。
    image: mysql:latest
    # volume > 仮想環境上でのファイルをパスを指定してアクセスできるようにします。パスはこのymlファイルがあるディレクトリ基準です
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/mysql-config.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/init:/docker-entrypoint-initdb.d
        # environment > 環境変数を指定します。Compose実行時に指定されるものにあたります。
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: database
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'

    # (例2)goを使う場合であれば以下のようになります。細かい詳細は解説にて説明、参照します。
    go:
        build:
          context: ./go/
          target: dev
        volumes:
          - ./go/src:/go/src:cached
        working_dir: /go/src
        ports:
          - 8080:8080
        tty: true
        env_file:
          - ./go/.env

MySQL以外の環境を構築する場合は3. へ飛んでください。

2. MySQL環境用のソースコード

MySQLの場合に初期DBを用意する必要があるので、設定ファイル(mysql-config.cnf)と初期テーブル(001-create-tables.sql)のコードを書き、それを実行するコード(init-database.sh)を書きます。

### mysql-config.cnf ###
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4
--- 001-create-tables.sql ---
---- drop ----
DROP TABLE IF EXISTS `first_table`;

---- create ----
create table IF not exists `first_table`
(
 `id`               INT(20) AUTO_INCREMENT,
 `name`             VARCHAR(20) NOT NULL,
 `created_at`       Datetime DEFAULT NULL,
 `updated_at`       Datetime DEFAULT NULL,
    PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
### init-database.sh ###
#!/usr/bin/env bash
#wait for the MySQL Server to come up
#sleep 90s

#run the setup script to create the DB and the schema in the DB
mysql -u docker -pdocker test_database < "/docker-entrypoint-initdb.d/001-create-tables.sql"

3. Docker起動

imageを指定していない場合(例2)、ビルドを行い、イメージを構築します。この時に参照されるのがよく巷で聞くDockerfileです。今回はMySQL環境を爆速で仕上げるため省略しますが、解説にて説明します。

今回は使用するイメージ(image)を

# docker-compose.yml
version: '3'
services:
  # (例1)ここに使用するサービスを書きます
  mysql:
    # image > コンテナ実行時の元になるイメージです。使うサービスのリポジトリ名だったりを書きます。
    image: mysql:latest

ここで指定しているので、コンテナを作成し起動するところからで大丈夫です。

$ docker-compose up
Creating network "backend_default" with the default driver
Creating backend_mysql_1 ... done
Creating backend_go_1    ... done
...

上記コマンドで起動できたら、同じ階層にいる別ターミナルで以下のコマンドによって起動を確認してみてください。

$ docker-compose ps   
     Name                   Command             State           Ports         
------------------------------------------------------------------------------
backend_go_1      bash                          Up      0.0.0.0:8080->8080/tcp
backend_mysql_1   docker-entrypoint.sh mysqld   Up      3306/tcp, 33060/tcp

ここで起動し、サービス名が表示されていれば構築完了です!早いですね。

最後に以下コマンドによってコンテナとそれに関わるネットワークを停止します。

$ docker-compose down
Stopping backend_mysql_1 ... done
Stopping backend_go_1    ... done
Removing backend_mysql_1 ... done
Removing backend_go_1    ... done
Removing network backend_default

下記コマンドを叩いたターミナルが

$ docker-compose up
Creating network "backend_default" with the default driver
Creating backend_mysql_1 ... done
Creating backend_go_1    ... done
...
mysql_1  | 2020-11-30T14:42:11.612252Z 0 [System] [MY-013172] [Server] Received SHUTDOWN from user <via user signal>. Shutting down mysqld (Version: 8.0.22).
go_1     | root@f1d2304a041d:/go/src# exit
backend_go_1 exited with code 0
mysql_1  | 2020-11-30T14:42:12.324000Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.22)  MySQL Community Server - GPL.
backend_mysql_1 exited with code 0

上記のようにexitできていれば、無事に停止しています。

これでDocker-composeによって仮想環境が構築できました。

4. MySQLでsample-tableを生成

仮想環境に入り、init-database.shを叩きます。

コンテナ内で起動中にコマンドを叩く場合は以下を参考にしてください。

# コンテナ起動
$ docker-compose up
...

> 別ターミナルでdocker環境に入り、コマンド実行
# 権限付与 > ここでyml中に書いたvolumesで、パスを指定できる、と言うわけです
$ docker-compose exec mysql bash -c "chmod 0775 docker-entrypoint-initdb.d/init-database.sh"
# init-database.shを叩く
$ docker-compose exec mysql bash -c "./docker-entrypoint-initdb.d/init-database.sh"

解説

最初にアジェンダ書いてた時は、ここにがっつり解説書いていこうと思ったのですが、思ったより上で説明してしまった感が否めないです。書くことなくなってきた。。。まずいぞ。。。

そもそもdocker-composeって??

複数のコンテナを、簡易に管理し、実行するDockerアプリケーションのためのツールの1つです。
コマンドを1つ実行するだけで、docker-compose.ymlに定義した設定に基づいて環境を構築してくれるので、本当にいろんな使い方ができます!!、、(らしいですと言うのが本心。お恥ずかしい。)

ymlで(よく)使用するオプション

image:

コンテナを実行する時に元となるイメージを指定します。
自分でDockerfileを書いて実行する場合は、次のbuildオプションを使用します。
imageで使用する場合はビルドを行わず、$ docker-compose up からで大丈夫です。

build:

コンテナを実行する時に参照するDockerfileを指定します。外部imageではなく、自分でDockerfileを制作して使う場合はよく以下の構成で使用します。

自分でいちからDockerfileを書く、よりはリポジトリをクローンしてきて修正する、という扱い方が多いと思います。

├── docker
├── Dockerfile
└── docker-compose.yml
# docker-compose.yml
version: '3'
services:
  hoge:
    # 同階層のため相対パスで指定
    build: .

ここで自分でDockerfileを書いた場合はイメージを構築、すなわちビルドする必要があるので、初期起動前に以下コマンドを叩きます。

$ docker-compose build
db uses an image, skipping
Building go ......

使用するcontextの指定や、dockerfile生成に関してはリファレンスも参照することをお勧めします。
https://docs.docker.jp/compose/compose-file.html#context

environment:

主に仮想環境上で用いる環境変数を指定します。
配列、もしくはDictionaryで定義できるので、$ docker-compose execで実行する際にアクセスできるようにAPI鍵や今回のようにMySQLのホストの値を記載します。

env_file:

上記のenvironmentでは隠したい情報(自分はAPI鍵などはenv_fileを使用して.gitignoreで隠す、という形をとることが多いです)の場合は、これで環境変数を記載したファイルを指定します。

# docker-compose.yml
version: '3'
services:
    go:
        env_file:
          - ./go/.env

なお.envファイルは以下のように、変数 = 値の形を取ります。

# /go/.env
DB_USER=user
DB_PASSWORD=password
DB_HOST=mysql:3306
DB_DATABASE=database
DB_SOCKET=tcp

volumes:

自分のローカルファイルを環境上のファイルに割り当てるように指定することができます。

なお、実行手順にもコメントで書いた通り、ymlファイルがあるディレクトリを基準として相対パスは使用できます。

# docker-compose.yml
version: '3'
services:
  mysql:
    volumes:
      # ローカルファイル:仮想環境上のパス となります
      - ./mysql/data:/var/lib/mysql
      - ./mysql/mysql-config.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/init:/docker-entrypoint-initdb.d

ports:

ホスト側とコンテナ側、両者のポートを指定することができます。
(なお、コンテナのみの指定も可能)

# docker-compose.yml
version: '3'
services:
    go:
        ports:
        # ローカル側:仮想環境側 という感じです
          - 8080:8080
        env_file:
          - ./go/.env

docker-composeで(よく)使うコマンド

説明するコマンドを以下に書いておきます。このほうがササッと使いやすいですよね。

$ docker-compose build
$ docker-compose up
$ docker-compose down
$ docker-compose ps
$ docker-compose start
$ docker-compose stop

$ docker-compose build

image:を指定せずにbuild:を使用する際に使います。これによりサービスをビルドします。
上記にも書いた通り、image:を指定しない場合は、以下の$ docker-compose upの前にビルドする必要があります。

頻度の高いオプション
--no-cache 構築時にイメージのキャッシュを使わない

$ docker-compose up

仮想環境の立ち上げを行います。
実際はコンテナを構築、作成し、起動、アタッチまでを全てこのコマンドで行ってくれます。

なお、-d をつけることによって、バックグラウンドで実行されるため、コンテナは起動し続ける状態を確保できます。CIなどを回す際にはこのオプションを使うと便利です。

頻度の高いオプション
-d バックグラウンドでコンテナを実行

$ docker-compose down

起動している仮想環境を停止し、$ docker-compose up によって立ち上げたコンテナとネットワークを削除します。

頻度の高いオプション
--rmi all 全イメージを削除

$ docker-compose ps

起動しているコンテナの一覧を表示できます。

$ docker-compose exec

起動している環境に対して、任意のコマンドを実行することができます。
なので、基本的に環境上でプロンプトを動かしたりする際はこれを使用します。

# exec後の部分でどのコンテナを叩くか指定します
$ docker-compose exec mysql bash -c "./docker-entrypoint-initdb.d/init-database.sh"

# 以下コマンドのように扱えば、直接シェルを叩くこともできますが、volumesにてデフォルトの階層を指定する必要があります
$ docker-compose exec hogehoge /bin/bash
頻度の高いオプション
-u 指定されたユーザによりコマンドを実行

$ docker-compose start

既存のコンテナをサービスとして起動します。
$ docker-compose upすれば起動するので、$ docker-compose downではなく(停止後コンテナを削除するため)、$ docker-compose stop などで停止した場合に再び起動する時に使用します。

$ docker-compose stop

稼働中のコンテナを停止しますが、$ docker-compose down とは違い、削除しません。 上記の$ docker-compose start コマンドで、再起動できます。

まとめ

docker-composeによる仮想環境構築でした。
運用だと、docker-compose.ymlに複数コンテナを組み合わせて一挙に立てて使用することになることがおおいですね。

初日ということでしたが、自分が実装していた時にメモしていたことなどを噛み砕いて書いていくうちにどんどんボリュームが増しちゃいました。(笑)

ただ、語彙的にもとっつかみやすい文章になってるんじゃないでしょうか、、と思っています、、、(そうでなければ僕の語彙力の問題ですね、反省します)

皆様のますますのご活躍の少しでもお手伝いになれば、ということを書き締めたいと思います。

また質問等あればコメントにてご指摘などよろしくお願いします。

参考資料

  • docker-compose オプション / リファレンス

https://docs.docker.jp/compose/compose-file.html

  • docker-compose コマンド / リファレンス

https://docs.docker.jp/compose/reference/index.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker イメージの断捨離を圧倒的に効率化する

こういうのを作りました。

Docker イメージの断捨離がダルい問題

Docker イメージがディスク容量を圧迫して困ることがありますよね。

docker image prunedocker system prune をすれば dangling なイメージをまとめて削除することができます。danglings (宙ぶらりんな) イメージとは「タグ付けされていない」「どのコンテナも使ってない」イメージのことなので、つまりは「削除しても問題なさそうなイメージをまとめて削除する」ってやつです。

image.png

これだけである程度は削除できますが、タグ付けされている不要なイメージまでは削除できません。 -a / --all オプションを使えばタグ付けされている未使用なイメージをまとめて削除できますが、これだと今度は必要なイメージまで削除してしまうので難しいです。

結局、ちゃんと断捨離しようと思ったら目視でひとつひとつ削除していくしかないのです。

しかしそれが大変で、latest ではないイメージは <Repository>:<Tag> と指定するかイメージ ID を直接指定するしかなく、手でポチポチとコピペしていくハメになります。しかも削除順序を間違えると「このイメージはこっちのイメージが依存しているから削除できんよ」などとエラーを吐かれます。やってられるか。

こうした単純作業は効率化するのが IT エンジニアの美徳です。

「リストの中から人間が選択をする」操作を効率化する手段といえば何ですか?
そう、peco ですね。

peco とは

@xtetsuji さんの「pecoの基礎の基礎」がわかりやすいです。

とてもシンプルなツールで 「標準入力から受けた行データをインクリメンタルサーチして、選択した行を標準出力に返す」 コマンドです。

シンプルゆえに様々な組み合わせで効果を発揮します。あらゆる場面での選択肢を標準入力に渡して、選択された結果を標準出力から受け取って加工してコマンド実行をする、というのが基本的な流れ。

自分も「コマンド履歴を検索して再実行」とか「SSH の接続先を選んで接続」とか「Git リポジトリのブランチを選んで切り替え」とか1 頻繁に使います。 控えめに言ってめちゃくちゃ便利。

macOS だったら brew install peco で入ります。

実はあまり知られていないっぽいですが、 peco は複数行選択ができます。
今回はこれを使って Docker イメージを断舎離するやつを作っていきましょう。

peco で Docker イメージを選択

peco で複数行選択をするためには control + space を使いますが、macOS ではこの組み合わせは「入力ソースの切り替え」に割り当てられているので、そのままでは使えません。回避方法は何でもいいですが、今回は「tab で複数行選択」が出来るように peco の設定ファイルを書いておきます。

~/.config/peco/config.json
{
  "Keymap": {
    "Tab": "peco.ToggleSelectionAndSelectNext"
  }
}

さて、おもむろに以下のコマンドを実行してみましょう。

$ docker images | peco

docker images の結果を peco に渡して、peco でフィルタリングして、標準出力に流しただけです。

というわけで、Docker イメージをフィルタリングして選択するやつができました。

便利スクリプトを書く

あとはもういい感じに仕上げるだけです。

~/.zshrc
function peco-docker-images() {
  local images="$(docker images | tail +2 | sort | peco --prompt 'DOCKER IMAGES>' | awk '{print $3}' ORS=' ')"
  [ -z "$images" ] && return
  BUFFER="$LBUFFER$images$RBUFFER"
  CURSOR=$#BUFFER
}

zle -N peco-docker-images
bindkey '^x^i' peco-docker-images

イメージを削除するために必要なのはイメージ ID なので、peco の結果に対して awk でイメージ ID だけを取り出して space で結合しています。

これは zsh スクリプトですが、bash でも似たような雰囲気で掛けるのではないでしょうか。(知らん)

この例では control + x control + i で peco が起動して、選択したイメージ ID がプロンプトのバッファに差し込まれます。

これで地道なコピペをすることなく Docker イメージをごっそり断捨離できるようになりました。めでたしめでたし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む