20200916のdockerに関する記事は22件です。

マルチステージによるDockerイメージの軽量化-Reactアプリ編

はじめに

はじめまして、最近Docker、Kubernetes辺りをいじるのにハマっております。
今回はDockerイメージの軽量化に挑戦してみました!

今回は create-react-app コマンドを用いてデフォルトで作成したものをコンテナ化しております。

  • 事前準備
    • create-react-appでアプリ作成
// アプリ作成
$ create-react-app sample-react-app
$ cd sample-react-app

問題のDockerfile

Dockerfileを準備します。流れとしては
- production用にbuild
- 静的ファイルを実行するモジュールをインストール、実行

という感じです。

Dockerfile
FROM node:12
COPY . /react-app
WORKDIR /react-app
RUN npm install && npm run build && npm install -g serve
CMD ["serve","-s","build" ]

これでデフォルトだと5000番でListenするので以下のようにポートフォワードして実行すれば localhost:5000 でアプリが動くことが確認できると思います。

$ docker run -p 5000:5000 sample-react-app:0.0.1

昨日までの私はおー!Reactアプリコンテナ化できたじゃんと満足していました。
けど思いました。

最終的に静的ファイル実行するだけなんだからnodeの環境要らなくない?

ちなみにここでDockerイメージのサイズを見てみると...

$ docker images
REPOSITORY                                                   TAG                 IMAGE ID            CREATED             SIZE
sample-react-app                                             0.0.1               6e7bc9096dd7        9 minutes ago       1.31GB

...でかいっす。

ただ、gitにproductionのファイル群をあげたりはそんなにしないかなと思うので、CIツールとか使ってgitのリポジトリからビルドしていくならnpm install, npm run buildはDockerでビルドしていく段階で欲しいと思うのです。

そこでマルチステージ!これがあるじゃん。

ということでマルチステージを使ってproductionのファイル群をnginxの方に持っていく、というのをやってみました。

改良Dockerfile

改良Dokerfile
#第一段階(productionファイル生成まで)
FROM node:12 as node
COPY . /react-app
WORKDIR /react-app
RUN npm install && npm run build

#第二段階(一段階目のコンテナの中身から静的ファイル群だけをコピーする)
FROM nginx:1.19.2-alpine
COPY --from=node ./react-app/build /usr/share/nginx/html
CMD nginx -g "daemon off;"

ドキドキの起動です。。。タグを0.0.2にしてビルド、実行してみます

$ docker build -t sample-react-app:0.0.2 .
$ docker run -p 5000:80 sample-react-app:0.0.2

無事起動できました!
image.png

そしてサイズは...

$ docker images
REPOSITORY                                                   TAG                 IMAGE ID            CREATED             SIZE
sample-react-app                                             0.0.2               9148ac06ddbe        3 minutes ago       22.5MB
かなりスマートになりました!サイズだけなら1/60になりました!

おわりに

個人的にここまで劇的に変えられたと結構な達成感を持てました!
今後はコンテナ化だけで満足せず、どのように作っていくかもじっくり考えてみたいと思います。

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

k8sのマニフェストでPodのdocker imageのCMDを書き換えるときはcommandじゃダメ

ちゃんとドキュメントを読めば書いてありました。
ただ、間違えやすそうなのでメモ。

結論

  • マニフェストのcommandはDockerfileのENTRYPOINT
  • マニフェストのargsはDockerfileのCMD
apiVersion: v1
kind: Pod
metadata:
  name: command-demo
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    command: ["printenv"]                    # こっちはENTRYPOINT
    args: ["HOSTNAME", "KUBERNETES_PORT"]    # こっちはCMD
  restartPolicy: OnFailure

注意

また、command・argsをそれぞれ定義したかどうかで挙動が変わるので注意。

Dockerfileが

  • ENTRYPOINT: echo
  • CMD: HOSTNAME

だった場合

command args 実行されるコマンド
未定義 未定義 echo HOSTNAME
printenv 未定義 printenv
未定義 KUBERNETES_PORT echo KUBERNETES_PORT
printenv KUBERNETES_PORT printenv KUBERNETES_PORT

となる。

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

Docker Hub代わりのGitHub Container Registry入門

概要

Docker Hubの無料枠に制限がかかるという話もあり、GitHub Container Registryの使い方を調べたのでメモする。具体的には以下を実現したい。

  1. GitHubへpushする度、GitHub Actionsでdockerイメージをbuildし、GitHub Container Registryにpush
  2. その際、GitHubへpushしたタグに応じて、dockerイメージのタグも変更する

環境

  • wsl2のDebian
  • Docker version 19.03.12, build 48a66213fe

事前準備

GitHub Container Registryに自動でdockerイメージをpushするには、Personal Access Tokenが必要1。GitHub右上の自分のアイコンからSettings > Developer settings > Personal access tokens > Generate new tokenの手順で作成する。スコープは以下の画像のもので大丈夫そう。
image.png

得られたトークンはDockerfileなどを管理しているGitHubリポジトリでSetting > Secrets > New secretの手順で登録しておく(ここではCR_PATという名前で保存したことにする)。

YAMLファイル作成①

GitHub Actionsを制御するためのYAMLファイルを作成する。以下のようなディレクトリ構成を想定2

.
├── .git
├── .github
│   └── workflows
│       └── build_on_push.yaml
└── Dockerfile

build_on_push.yamlの内容は以下の通り。

name: Publish Docker image
on: push
jobs:
  main:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.CR_PAT }}
      - name: Push to GitHub Container Registry
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ghcr.io/dr666m1/qiita_starwars:latest

name:はworkflowやjobを命名しているだけなので任意の値。on:ではトリガーとなるイベントを指定(ここではpush)。3行目のjobs:で5つのstepからなるjobを定義しているので、以降で各stepについて簡単に解説する(ちなみにruns-on:はjobを実行する仮想マシンの指定で、ここではGitHubホストランナーを選択している)。

Checkout

actions/checkout@v2というactionで、GitHubリポジトリにアクセスできる状態にする。逆に言えば、このstepがないと「Dockerfileが見つかりません」といったエラーに陥るはず。ドキュメントはこちら

Set up QEMU, Set up Docker Buildx

docker/setup-qemu-action@v1 docker/setup-buildx-action@v1はいずれも必要な機能を利用可能にするためのactionという程度の認識でよいと思う。

Login to GitHub Container Registry

docker/login-action@v1というactionでログイン処理を行う。${{ secrets.CR_PAT }}は先ほど作成したPersonal Access Tokenを参照している。Docker Hub やGitLabにも対応していて、ドキュメントにはそれぞれの対応方法が記載されている。

Push to GitHub Container Registry

docker/build-push-action@v2というactionでdockerイメージのbuildとpushを行う。tags:でタグも指定できるが、この段階ではlatestとしておく。

動作確認

ファイルの準備ができたらgit pushするだけで、YAMLで定義したワークフローが実行される。以下の画面のようにログも確認できる。

image.png

問題なければGitHub右上の自分のアイコンからYour profile > Packagesでbuildされたdockerイメージを確認できる。デフォルトでprivateになるようなので、publicにしたければ変更してしまう。あとはdocker runして問題なければOK3

docker run -it --rm ghcr.io/dr666m1/qiita_starwars:latest

YAMLファイル作成②

次にタグを指定できるよう、YAMLファイルを以下の通り変更する。

name: Publish Docker image
on: push
jobs:
  main:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Prepare # 追加
        id: prep
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            TAG=${GITHUB_REF#refs/tags/}
          else
            TAG="latest"
          fi
          echo "::set-output name=tag::${TAG}"
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.CR_PAT }}
      - name: Push to GitHub Container Registry
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ghcr.io/dr666m1/qiita_starwars:${{ steps.prep.outputs.tag }} # 変更

主な変更点はPrepareというstepの追加。run:でshell(デフォルトはbash4)で実行されるコマンドを指定する。ここでは$GITHUB_REF環境変数5によって処理を変えていて、タグの名前を含めばそれを、なければlatestTAGに代入している。echo "::set-output name=tag::${TAG}"ワークフローコマンドと呼ばれるもので、ここではTAGの値を後続のstepで参照できるようにしている。${{ steps.prep.outputs.tag }}が実際に参照している部分。

この変更によって、GitHubにpushしたタグに応じてdockerイメージのタグも変更されるようになる。例えば以下のように実行すれば、dockerイメージのタグも1.0になる。

git tag 1.0
git push --tags

image.png

最後に

もしDocker HubからGitHub Container Registryに移行したくなっても、これで概ね困らなそう。GitHub Actionsは自動テストなど、もっといろいろなことに使えるはずなので、時間があるときに勉強したい。


  1. Github Packagesでは、代わりにGITHUB_TOKENが使えたらしい 

  2. 実際に使ったGitHubリポジトリはこちら 

  3. Dockerfileを見ての通りだが、この例だとスターウォーズの上映が始まる(telnet towel.blinkenlights.nl)のでCtrl+] > quitで抜ける 

  4. 利用可能なshellは公式ドキュメントを参照 

  5. 利用可能な環境変数は公式ドキュメントを参照 

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

Django + Docker

はじめに

この記事ではDjango + Dockerのアプリ開発でおきたエラーとその対処法を書いていきます。

ERROR: No container found for web_1

いろいろ試してみてたのですが、一向に対処法がわからなかったのですが、次のようにターミナルに打ち込めば解決しました。

terminal
$ docker-compose up -d

参考記事
ERROR: No container found for web_1 #11045

TypeError: Field 'id' expected a number but got datetime.datetime(2020, 9, 16, 2, 52, 51, 44897, tzinfo=).

models.pyのForignkeyがあるところに次のように追記しました。

models.py
user = models.OneToOneField(
        User,
        verbose_name='ユーザー',
        on_delete=models.CASCADE,
        default=1   #追記
    )

これで解決しました。

django.db.utils.OperationalError: could not translate host name "db" to address: Name or service not known

sqlite3を使っていたのですが、このエラーが頻繁に出るので、Postgresqlを導入しました。

手順としてはこちらを参照してください。

ERROR: yaml.scanner.ScannerError: while scanning for the next token

このエラーでは「docker-compose.yml」のインデントを確認してみてください。
コメントアウトしているものでも、インデントがずれていると怒られるので注意してください。

yaml.parser.ParserError: while parsing a block mapping

このエラーでも「docker-compose.yml」のインデントを確認してみてください。

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

[Docker] Hugo を利用するための環境構築

概要

本記事では、Hugo(静的サイトジェネレーター) を Docker で動かすための仮想環境を作る手順についてまとめる

環境

VSCodeのインストール
参考:VSCodeでDocker入門

手順

利用可能な image を探す(仮想環境(コンテナ)つくる材料)

Docker で環境構築する際に、すでに利用することができる image がないか調べる

Hugo の公式サイト で、Docker と調べると、公式が推奨する image があったので、以下を利用する。
スクリーンショット 2020-09-15 15.24.17.png
https://hub.docker.com/r/klakegg/hugo/

docker-compose.yml を作成

klakegg/hugo に記載されている以下のサンプルを元に docker-compose.yml を作成する
スクリーンショット 2020-09-15 15.31.22.png

本記事では、仮想環境の中で「hugo」のコマンドをたたくことを目標とするので、以下のように記述した

docker-compose.yml
version: '3'
services: 
    hugo:
        image: klakegg/hugo:0.74.3-ubuntu
        volumes:
            - ".:/src"
        entrypoint: bash
        ports:
            - "1313:1313"
        tty: true
        working_dir: /src

それぞれのタグについて

docker-compose.yml
version: '3' # 最新版は'3' 
services: # 固定
    servicename: # 任意のサービスの名前をつける
        image: イメージ名: タグ名  # Docker Hub から利用する image を指定
        volumes:
            - ローカルのフォルダのパス:コンテナのフォルダのパス #ローカルと仮想マシンのフォルダの紐付け
        entrypoint: 処理  # entrypoint: デフォルトの entrypoint の上書きしたいときに記載
        ports: # 「hugo server」を実行したいため
            - "ホスト側:コンテナ側"
        tty: true      # Attach Shell でコンテナに入る時は必須
        working_dir: コンテナのフォルダパス  #コンテナ内のワーキングディレクトリ

port について補足

「hugo server」 を使うことで、hugo で作成した画面を確認することができる
そこで、デフォルトでは port 1313 を利用するが、コンテナ側で起動した server をローカルから利用するために は、ローカルとコンテナのポートを繋げる必要がある
スクリーンショット 2020-09-16 11.00.52.png

仮想環境の構築コマンドを実行

docker-compose up -d

スクリーンショット 2020-09-15 16.52.57.png
コンテナの作成完了! [Attach Shell] をクリックし、作成したコンテナに入る
スクリーンショット 2020-09-15 17.33.48.png
「hugo version」 と入力し、hugo のバージョンが返って来れば OK
スクリーンショット 2020-09-15 17.34.10.png

image をklakegg/hugo:0.74.3-ubuntuにした理由

当初は、[Default minimal image based upon Busybox:] にある [Hugo 0.74.3: 0.74.3] の利用を検討していたが、「bash」コマンドが使えなかった。そのため、このコマンドが利用できる [hugo:0.74.3-ubuntu]を採用した。以下に変更までの経緯を示す

Docker hub でタグの確認

スクリーンショット_2020-09-15_16_32_42.png

image の中身について [Tags] で検索し、確認

スクリーンショット 2020-09-15 16.35.44.png

Hugo 0.74.3 では、ENTRYPOINT で hugo が呼び出されている。
スクリーンショット_2020-09-15_16_35_27.png
「hugo」コマンドの実行は起動時ではなく、任意のタイミングで行いたい

docker-compose.yml の修正

entrypoint: bashと書くことで image の entrypoint を上書きし、起動時に何もしないようにする。

docker-compose.yml
version: '3'
services: 
    hugo:
        image: klakegg/hugo:0.74.3
        volumes:
            - ".:/src"
        entrypoint: bash
        ports:
            - "1313:1313"
        tty: true
        working_dir: /src

仮想環境の構築

仮想環境の構築コマンドを実行する

docker-compose up -d

エラーが発生し、コンテナが作成できない。

スクリーンショット 2020-09-15 16.51.53.png
デフォルトのBusyBoxタグ では bash コマンドが使えない

参考

docker-compose.ymlの書き方について解説してみた
Docker Hub の概要
Compose ファイル リファレンス

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

【備忘録】Docker版 Db2環境 取得手順

1. はじめに

以前にLinux環境にDb2を導入する手順をまとめました。

【備忘録】Db2 インストール手順まとめ(Linux)
https://qiita.com/Haruka-Ogawa/items/9576399209b32f3016ea

Db2は 2017年から Docker版が提供されおり、
Docker storeからpullすることで より手軽にDb2環境を使用することが可能です。

今回は、DockerのDb2コンテナを取得する手順をまとめます。

2. 準備

PC に Docker を導入しておきます。
今回は Mac PC に Docker v19.03.12 を導入しています。

$ docker -v
Docker version 19.03.12, build 48a66213fe

3. 作業

3-1. イメージ取得

まず、Db2イメージを取得します。

イメージの取得 には docker pullコマンドを使用します。
以下リンク先を開き、画面右側にある Db2イメージ取得のための docker pull コマンド をコピーします。

https://hub.docker.com/r/ibmcom/db2

image.png

コピーした docker pull コマンドを実行します。

$ docker pull ibmcom/db2
Using default tag: latest
latest: Pulling from ibmcom/db2
524b0c1e57f8: Pull complete 
e7a9171c839a: Pull complete 
f4447742b873: Pull complete 
db90696d3502: Pull complete 
6acc1312ab24: Pull complete 
67e9a72bc07a: Pull complete 
2b9e6b7678c7: Pull complete 
Digest: sha256:cf95dd272a4f99d7f9119945ecad04b64ad3a2badc8a6682f1105aa87279da60
Status: Downloaded newer image for ibmcom/db2:latest
docker.io/ibmcom/db2:latest

[参考] Docker ドキュメント:pull
https://docs.docker.jp/engine/reference/commandline/pull.html

3-2. イメージ確認

docker images コマンドで 取得したイメージ一覧を表示し、
db2イメージ が取得されたことを確認します。

$ docker images
REPOSITORY               TAG                  IMAGE ID            CREATED             SIZE
ibmcom/db2               latest               d6b3abc02d57        2 months ago        2.69GB

[参考] Docker ドキュメント: images
https://docs.docker.jp/engine/reference/commandline/images.html

3-3. コンテナ作成・起動

docker runコマンドを実行し、Db2 コンテナ を作成・起動します。
今回実行したコマンドは、 Docker Hub:ibmcom/db2 のガイド を参考にしています。

$ docker run -itd --name mydb --privileged=true -p 50000:50000 -e LICENSE=accept -e DB2INST1_PASSWORD=passw0rd -e DBNAME=testdb -v /Users/ogawa/db2fs:/database ibmcom/db2
67070db8cf20d82a9be8b45454eb1d5239c6c4d33b685c8ab9503a3bcc84e68b

[参考] Docker Hub:ibmcom/db2
https://hub.docker.com/r/ibmcom/db2

[参考] Docker ドキュメント: run
https://docs.docker.jp/engine/reference/commandline/run.html

3-4. コンテナ ログ確認

docker logsコマンドで コンテナのログを確認し、
コンテナが作成・起動されたことを確認します。

$ docker logs -f mydb
(*) Previous setup has not been detected. Creating the users... 
(*) Creating users ...
(*) Creating instance ... 
DBI1446I  The db2icrt command is running.


DB2 installation is being initialized.

 Total number of tasks to be performed: 4 
Total estimated time for all tasks to be performed: 309 second(s) 

Task #1 start
Description: Setting default global profile registry variables 
Estimated time 1 second(s) 
Task #1 end 

Task #2 start
Description: Initializing instance list 
Estimated time 5 second(s) 
Task #2 end 

Task #3 start
Description: Configuring DB2 instances 
Estimated time 300 second(s) 
Task #3 end 

Task #4 start
Description: Updating global profile registry 
Estimated time 3 second(s) 
Task #4 end 

The execution completed successfully.

For more information see the DB2 installation log at "/tmp/db2icrt.log.77".
DBI1070I  Program db2icrt completed successfully.


09/09/2020 03:16:06     0   0   SQL1032N  No start database manager command was issued.
SQL1032N  No start database manager command was issued.  SQLSTATE=57019
(*) Cataloging existing databases
ls: cannot access /database/data/db2inst1/NODE0000: No such file or directory
(*) Applying Db2 license ...

LIC1402I  License added successfully.


LIC1426I  This product is now licensed for use as outlined in your License Agreement.  USE OF THE PRODUCT CONSTITUTES ACCEPTANCE OF THE TERMS OF THE IBM LICENSE AGREEMENT, LOCATED IN THE FOLLOWING DIRECTORY: "/opt/ibm/db2/V11.5/license/en_US.iso88591"
(*) Saving the checksum of the current nodelock file ...
(*) Updating DBM CFG parameters ... 
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed 
successfully.
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed 
successfully.
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed 
successfully.
No Cgroup memory limit detected, instance memory will follow automatic tuning
(*) Remounting /database with suid... 
(*) Nothing appears in the Db2 directory. will skip update/upgrade.
(*) Code level is the same. No update/upgrade needed.

DB2 State : Operable
DB2 has not been started
Starting DB2...

09/09/2020 03:16:35     0   0   SQL1063N  DB2START processing was successful.
SQL1063N  DB2START processing was successful.
(*) User chose to create testdb database
(*) Creating database testdb ... 
DB20000I  The CREATE DATABASE command completed successfully.
DB20000I  The ACTIVATE DATABASE command completed successfully.
09/09/2020 03:18:38     0   0   SQL1026N  The database manager is already active.
SQL1026N  The database manager is already active.
### Enabling LOGARCHMETH1

   Database Connection Information

 Database server        = DB2/LINUXX8664 11.5.4.0
 SQL authorization ID   = DB2INST1
 Local database alias   = TESTDB

DB20000I  The UPDATE DATABASE CONFIGURATION command completed successfully.
SQL1363W  One or more of the parameters submitted for immediate modification 
were not changed dynamically. For these configuration parameters, the database 
must be shutdown and reactivated before the configuration parameter changes 
become effective.
### Restarting DB2
09/09/2020 03:18:46     0   0   SQL1064N  DB2STOP processing was successful.
SQL1064N  DB2STOP processing was successful.
09/09/2020 03:18:51     0   0   SQL1063N  DB2START processing was successful.
SQL1063N  DB2START processing was successful.
### Making backup directory and performing backup

Backup successful. The timestamp for this backup image is : 20200909031855

(*) Applying autoconfiguration for instance ... 

   Database Connection Information

 Database server        = DB2/LINUXX8664 11.5.4.0
 SQL authorization ID   = DB2INST1
 Local database alias   = TESTDB

DB20000I  The UPDATE DATABASE CONFIGURATION command completed successfully.
SQL1363W  One or more of the parameters submitted for immediate modification 
were not changed dynamically. For these configuration parameters, the database 
must be shutdown and reactivated before the configuration parameter changes 
become effective.
DB20000I  The UPDATE DATABASE CONFIGURATION command completed successfully.
DB20000I  The UPDATE DATABASE CONFIGURATION command completed successfully.
SQL1363W  One or more of the parameters submitted for immediate modification 
were not changed dynamically. For these configuration parameters, the database 
must be shutdown and reactivated before the configuration parameter changes 
become effective.
DB20000I  The SQL command completed successfully.
09/09/2020 03:19:15     0   0   SQL1064N  DB2STOP processing was successful.
SQL1064N  DB2STOP processing was successful.
09/09/2020 03:19:20     0   0   SQL1063N  DB2START processing was successful.
SQL1063N  DB2START processing was successful.
(*) Skipping TEXT_SEARCH setup for database testdb because TEXT_SEARCH is not configured for the instance ...
ssh-keygen: generating new host keys: RSA1 RSA DSA ECDSA ED25519 
(*) All databases are now active. 
(*) Setup has completed.
FALSE

2020-09-09-03.19.20.117938+000 I223959E393           LEVEL: Warning
PID     : 14939                TID : 139881437169536 PROC : db2start
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
FUNCTION: DB2 UDB, base sys utilities, sqleReleaseStStLockFile, probe:16076
MESSAGE : Released lock on the file:
DATA #1 : String, 50 bytes
/database/config/db2inst1/sqllib/ctrl/db2strst.lck

2020-09-09-03.19.37.389298+000 I224353E420           LEVEL: Warning
PID     : 14947                TID : 140530994374400 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 15                   EDUNAME: db2wlmtm 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

2020-09-09-03.20.07.369457+000 I224774E420           LEVEL: Warning
PID     : 14947                TID : 140530994374400 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 15                   EDUNAME: db2wlmtm 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

2020-09-09-03.20.37.353793+000 I225195E420           LEVEL: Warning
PID     : 14947                TID : 140530994374400 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 15                   EDUNAME: db2wlmtm 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

2020-09-09-03.21.07.337071+000 I225616E420           LEVEL: Warning
PID     : 14947                TID : 140530994374400 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 15                   EDUNAME: db2wlmtm 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

2020-09-09-03.24.02.151703+000 I226037E420           LEVEL: Warning
PID     : 14947                TID : 140530994374400 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 15                   EDUNAME: db2wlmtm 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

2020-09-09-03.24.02.031095+000 I226458E419           LEVEL: Warning
PID     : 14947                TID : 140530998568704 PROC : db2sysc 0
INSTANCE: db2inst1             NODE : 000
HOSTNAME: 67070db8cf20
EDUID   : 14                   EDUNAME: db2wlmt 0
FUNCTION: DB2 UDB, base sys utilities, sqleTimedSleep, probe:1182
DATA #1 : unsigned integer, 8 bytes
1.59962E+12
DATA #2 : unsigned integer, 8 bytes
1.59962E+12

[参考] Docker ドキュメント:logs
https://docs.docker.jp/engine/reference/commandline/logs.html

3-5. コンテナ確認

docker psコマンドで コンテナの一覧を表示し、
Db2コンテナの状態を確認します。

$ docker ps -a
CONTAINER ID        IMAGE                                       COMMAND                  CREATED             STATUS                       PORTS                                                          NAMES
67070db8cf20        ibmcom/db2                                  "/var/db2_setup/lib/…"   About an hour ago   Up About an hour             22/tcp, 55000/tcp, 60006-60007/tcp, 0.0.0.0:50000->50000/tcp   mydb

[参考] Docker ドキュメント: ps
https://docs.docker.jp/engine/reference/commandline/ps.html

3-6. コンテナ内でコマンド実行

docker execコマンドは、コンテナ内で任意のコマンドを実行させることができます。
bash を起動し、db2inst1にスイッチすることで、db2環境に入ります。

$ docker exec -ti mydb bash -c "su - db2inst1"
Last login: Wed Sep  9 04:48:57 UTC 2020

[参考] Docker ドキュメント: exec
https://docs.docker.jp/engine/reference/commandline/exec.html

3-7. Db2環境 確認

Db2コマンドが実行できることを確認します。

  • db2のバージョン確認

db2levelコマンドを実行し、Db2のバージョンを確認します。

[db2inst1@67070db8cf20 ~]$ db2level
DB21085I  This instance or install (instance name, where applicable: 
"db2inst1") uses "64" bits and DB2 code release "SQL11054" with level 
identifier "0605010F".
Informational tokens are "DB2 v11.5.4.0", "s2006161200", "DYN2006161200AMD64", 
and Fix Pack "0".
Product is installed at "/opt/ibm/db2/V11.5".
  • データベース接続

docker runコマンド実行時点で、データベースtestdbが作成されています。
connectコマンドで、データベースtestdbに接続します。

[db2inst1@67070db8cf20 ~]$ db2 connect to testdb

   Database Connection Information

 Database server        = DB2/LINUXX8664 11.5.4.0
 SQL authorization ID   = DB2INST1
 Local database alias   = TESTDB
  • テーブル作成

データベースtestdbに、テーブルTBL1を作成してみます。

[db2inst1@67070db8cf20 ~]$ db2 "CREATE TABLE TBL1(COL1 INTEGER, COL2 VARCHAR(50))"
DB20000I  The SQL command completed successfully.
  • データベース切断

terminateコマンドで、データベースtestdbから切断します。

[db2inst1@67070db8cf20 ~]$ db2 terminate
DB20000I  The TERMINATE command completed successfully.

Db2コマンドを実行し、操作ができることを確認しました。

コンテナから出る時はexitコマンドを使用します。

[db2inst1@67070db8cf20 ~]$ exit
logout

4. コンテナ 起動・停止

作成したコンテナの起動・停止には、
docker startコマンド、docker stopコマンドを使用します。

4-1. 起動

docker startコマンドで、Db2コンテナ(mydb)を起動します。

$ docker start mydb
mydb

[参考] Docker ドキュメント: start
https://docs.docker.jp/engine/reference/commandline/start.html

4-2. 停止

docker stopコマンドで、Db2コンテナ(mydb)を停止します。

$ docker stop mydb
mydb

[参考] Docker ドキュメント: stop
https://docs.docker.jp/engine/reference/commandline/stop.html

5. おわりに

今回は、Dockerコンテナとして提供されているDb2をpullしてみました。

Dockerが導入されたPCさえあれば とても手軽にDb2環境を使用することができ、
作業自体も10分ちょっとくらいでできたので、ちょっとDb2の動作確認などしたい場合などに便利だと感じました。

参考情報

Db2とDocker
https://www.ibm.com/blogs/solutions/jp-ja/db2-docker/

Docker Hub:ibmcom/db2
https://hub.docker.com/r/ibmcom/db2

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

Dockerでタグのついていない不要なイメージの一括削除

# タグ付けされていないイメージの一括削除
docker rmi $(docker images -f dangling=true -q)

Docker-docs-ja|images
https://docs.docker.jp/engine/reference/commandline/images.html

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

Dockerでタグなしの不要なイメージを一括削除

はじめに

dockerを使っているとimageが容量を圧迫していきます。
不要なimageを削除する為のコマンドを備忘録の為、メモしておきます。

コマンド

# タグ付けされていないイメージの一括削除
docker rmi $(docker images -f dangling=true -q)

参考

Docker-docs-ja|images
https://docs.docker.jp/engine/reference/commandline/images.html

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

Dockerでタグなしimageを一括削除する方法

はじめに

dockerを使っているとimageが容量を圧迫していくことがあります。
1つ1つ削除するのも手間なので、タグなしimageを一括で削除する為のコマンドをメモしておきます。
コマンドのオプションの意味についても纏めておきます。

タグなしimageを一括削除コマンド

docker rmi $(docker images -f "dangling=true" -q)

上記のコマンドを実行すれば、タグなしimageを一括削除できます。

コマンドの説明

filterオプションでdanglingをtrueにすることで、タグなしimageを取得

docker images -f "dangling=true"
# 実行結果
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              c29504d0f8fd        2 weeks ago         1.21GB
<none>              <none>              e6325cdc18c4        2 weeks ago         1.18GB

quietオプションでIDのみを取得

docker images -f "dangling=true" -q
# 実行結果
c29504d0f8fd
e6325cdc18c4

取得したIDを使用してimageを削除

docker rmi $(docker images -f "dangling=true" -q)

詳しくは公式ドキュメントをご参照ください。

公式ドキュメント

Docker docs|docker images
https://docs.docker.com/engine/reference/commandline/images/
Docker docs|docker rmi
https://docs.docker.com/engine/reference/commandline/rmi/

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

ERROR: create _mysql-data: "_mysql-data" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed.

はじめに

※MySQL、Dockerのタグを付けましたが今回の内容には直接は関係ないです。

エラー内容

Docker立ち上げようとdocker-compose up -dしたら

ERROR: create _mysql-data: "_mysql-data" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. 

というエラーが出てしまいました。みなさんこんな初歩的なミスはしないためかググってもなかなか同じ内容のエラーで悩んでる方が出てきませんでした。

原因

git pull 〇〇でプロジェクトのURLをプルしたディレクトリ名をカタカナにしておりました。なので"[a-zA-Z0-9][a-zA-Z0-9_.-]"という風にアルファベットか数字にしてねと怒られていたのでした。。

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

Docker for WindowsをアップデートしたらWSL2のdockerがエラーを吐いた件の対応

$ docker-compose ps
ERROR: Couldn't connect to Docker daemon at http+docker://localhost - is it running?

If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.

あれ、なにかしたっけ・・・。

そういえばDocker for Windowsがなにかアップデートしたような・・・。

名称 バージョン
docker for desktop 2.3.0.5
Engine 19.03.12
Notary 0.6.1
Compose 1.27.2
Credential Helper 0.6.3
Kubernetes v1.16.5

とりあえずエラーをぐぐる

参考
WSL2にしたらDocker使えへん

この設定を行ったところ、無事にWSL2内で動作することを確認した

$ docker-compose ps
Name   Command   State   Ports
------------------------------

完了

何もなくてよかった。

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

dockerで手元のイメージを誰かに渡す方法

前提

自分のローカルで動いているdockerのイメージをプロジェクトメンバーに共有したい、という場面。

やり方

当然ですがweb上にイメージが落ちているのですから普通にdockerのコマンドでできます。

docker save コンテナ名 > my ファイル名.tar

これでtarファイルが生成されます。

読み込むときはこっち。

docker load < ファイル名.tar
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker+Sinatraで簡単なチャットアプリをつくる練習

Rubyの使い方をよく忘れるので、リハビリ用にRubyで簡単なWebアプリを作りました。また忘れた時のために、作る方法を書き残しておきます。

つくるのはNopochatというクソチャットアプリです。
投稿に「~も」というかわいらしい語尾が付くことで、言いづらい内容も心理的安全性を保ったまま送ることができる画期的なチャットですも。

image.png

なお本稿で紹介する内容はあくまでプログラムを書く練習のためのものであり、本番環境で運用することは想定していません。

環境

Dockerを利用します。Rubyのインストールは必要ありません。

  • Docker version 19.03.12
  • docker-compose version 1.26.2

Sinatraの立ち上げ

まず適当なディレクトリを切って、開発を始めます。

$ mkdir sinatra-chat && cd $_

プロジェクトをGitで管理する場合は、GitHubのgitignoreを取ってきてセットするのが良いと思います。

$ git init
$ curl https://raw.githubusercontent.com/github/gitignore/master/Ruby.gitignore -o .gitignore

https://hub.docker.com/_/ruby から使いたいバージョンのRubyイメージを取ってきてください。今回は2.7-slimを使います。

まずGemfileを初期化します。

$ docker run --rm --volume $(pwd):/app --workdir /app ruby:2.7-slim bundle init
$ docker run --rm --volume $(pwd):/app --workdir /app ruby:2.7-slim bundle add sinatra

GemfileGemfile.lockが生成されたのを確認したら、Dockerfileを記述していきます。

Dockerfile
FROM ruby:2.7-slim
WORKDIR /app

COPY Gemfile ./
COPY Gemfile.lock ./
RUN bundle config --local set path 'vendor/bundle'
RUN bundle install

CMD bundle exec ruby index.rb
docker-compose.yml
version: '3'
services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/vendor/bundle
    ports:
      - 127.0.0.1:4567:4567

アプリケーションの本体となるindex.rbを作成します。

index.rb
require 'sinatra'

configure do
  set :bind, '0.0.0.0'
end

get '/' do
  'Hello Sinatra!'
end

http://localhost:4567/ にアクセスし、Hello Sinatra!と表示されたら成功です

ブラウザリロードによる再読み込みの有効化

そのままでは、ファイルを編集してもSinatraサーバを再起動しなければ反映されません。
開発を始める前に、リロードでの再読み込みを有効にすると便利です。

$ docker-compose run --rm app bundle add sinatra-contrib
index.rb
 require 'sinatra'
+require 'sinatra/reloader' if settings.development?

チャット機能の開発

チャット内容は後で永続化するので、とりあえず今は@@chatsというクラス変数に格納していきます。

index.rb
get '/' do
  @@chats ||= []
  erb :index, locals: {
    chats: @@chats.map{ |chat| add_suffix(chat) }.reverse
  }
end

post '/' do
  @@chats ||= []
  @@chats.push({ content: params['content'], time: Time.now } )
  redirect back
end

def add_suffix(chat)
  { **chat, content: "#{chat[:content]}も" }
end

HTMLテンプレートにはerbを使います。views/というディレクトリを切って、そこにindex.erbを格納すると、erb :indexで呼び出すことができます。

views/index.erb
<form action="/" method="post">
  <input name="content" placeholder="投稿" />
  <button type="submit">送信</button>
</form>

<table>
  <% chats.each do |chat| %>
    <tr>
      <td><%= chat[:content] %></td>
      <td><%= chat[:time] %></td>
    </tr>
  <% end %>
</table>

これで最低限、チャットができるようになります。

データベースへの保存

チャット内容をMySQLに保存します。mysql2 Gemをインストールします。

$ docker-compose run --rm app bundle add mysql2

https://hub.docker.com/_/mysql からMySQLの好きなバージョンを取ってきて使います。またappの方にも接続情報を環境変数としてセットします。

docker-compose.yml
 version: '3'
 services:
   app:
     build: .
     volumes:
       - .:/app
       - /app/vendor/bundle
     ports:
       - 127.0.0.1:4567:4567
+    environment:
+      - MYSQL_HOST=db
+      - MYSQL_USER=root
+      - MYSQL_PASS=secret
+      - MYSQL_DATABASE=nopochat_development
+  db:
+    image: mysql:5.7
+    volumes:
+      - .:/app
+      - /var/lib/mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=secret
index.rb
 require 'sinatra'
 require 'sinatra/reloader' if settings.development?
+require 'mysql2'
(以下略)

mysql2 Gemの利用に必要なパッケージをインストールします。

Dockerfile
 FROM ruby:2.7-slim
 WORKDIR /app
+RUN apt-get update && apt-get install -y \
+  build-essential \
+  libmariadb-dev \
+  && apt-get clean \
+  && rm -rf /var/lib/apt/lists/*
(以下略)

セットした環境変数に基づいてデータベースクライアントを取得するメソッドを定義します。

index.rb
def db_client()
  Mysql2::Client.default_query_options.merge!(:symbolize_keys => true)
  Mysql2::Client.new(
    :host => ENV['MYSQL_HOST'],
    :username => ENV['MYSQL_USER'],
    :password => ENV['MYSQL_PASS'],
    :database => ENV['MYSQL_DATABASE']
  )
end

今回はGET /initializeにアクセスしたらデータベースを初期化する仕様とします(実際の運用ではこのような仕様はあり得ませんが...)

index.rb
get '/initialize' do
  client = Mysql2::Client.new(
    :host => ENV['MYSQL_HOST'],
    :username => ENV['MYSQL_USER'],
    :password => ENV['MYSQL_PASS']
  )
  client.query("DROP DATABASE IF EXISTS #{ENV['MYSQL_DATABASE']}")
  client.query("CREATE DATABASE IF NOT EXISTS #{ENV['MYSQL_DATABASE']} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci")
  client = db_client
  client.query(<<-EOS
      CREATE TABLE IF NOT EXISTS chats (
        id   INT AUTO_INCREMENT,
        name TEXT,
        content TEXT,
        time DATETIME,
        PRIMARY KEY(id)
    )
    EOS
  )
  redirect '/'
end

chatsというテーブルからデータを出し入れするメソッドを定義します。

index.rb
def chat_push(content, name="名無し")
  db_client.prepare(
    "INSERT into chats (name, content, time) VALUES (?, ?, NOW())"
  ).execute(name, content)
end

def chats_fetch()
  db_client.query("SELECT * FROM chats ORDER BY time DESC")
end

定義したメソッドを使って、GET /POST /を書き換えます。

index.rb
 get '/' do
-  @@chats ||= []
+  chats = chats_fetch
   erb :index, locals: {
-    chats: @@chats.map{ |chat| add_suffix(chat) }.reverse
+    chats: chats.map{ |chat| add_suffix(chat) }
   }
 end

 post '/' do
-  @@chats ||= []
-  @@chats.push({ content: params['content'], time: Time.now } )
+  chat_push(params['content'])
   redirect back
 end

アプリを起動して http://localhost:4567/initialize にアクセスすると、立ち上がります。
これでアプリを再起動しても、いちどチャットした内容が消えることはありません。

ログイン機能

セッションストレージにはDB(MySQL)を利用します。
ユーザ名とパスワードを持つusersテーブルと、セッションを保存するsessionsテーブルを定義します。なお本来passwordは暗号化してハッシュを持つようにします。また、sessionsの定期的な削除処理も必要です。

index.rb
  client.query(<<-EOS
    CREATE TABLE IF NOT EXISTS users (
      id   INT AUTO_INCREMENT,
      name VARCHAR(255) UNIQUE,
      password TEXT,
      PRIMARY KEY(id),
      INDEX key_index (name)
    );
    EOS
  )
  client.query(<<-EOS
    CREATE TABLE IF NOT EXISTS sessions (
      id   INT AUTO_INCREMENT,
      session_id VARCHAR(255) UNIQUE,
      value_json JSON,
      PRIMARY KEY(id),
      INDEX key_index (session_id)
    );
    EOS
  )
  user_push('admin', 'admin')

ユーザの追加・認証処理を定義します。

index.rb
def user_push(name, pass)
  db_client.prepare(
    "INSERT into users (name, password) VALUES (?, ?)"
  ).execute(name, pass)
end

def user_fetch(name, pass)
  result = db_client.prepare("SELECT * FROM users WHERE name = ?").execute(name).first
  return unless result
  result[:password] == pass ? result : nil
end

セッションの追加・取得処理を定義します。

index.rb
def session_save(session_id, obj)
  db_client.prepare(
    "INSERT into sessions (session_id, value_json) VALUES (?, ?)"
  ).execute(session_id, JSON.dump(obj))
end

def session_fetch(session_id)
  return if session_id == ""
  result = db_client.prepare("SELECT * FROM sessions WHERE session_id = ?").execute(session_id).first
  return unless result
  JSON.parse(result&.[](:value_json))
end

cookieを使うためrequire 'sinatra/cookies'を追加します。

index.rb
 require 'sinatra'
 require 'sinatra/reloader' if settings.development?
+require 'sinatra/cookies'
 require 'mysql2'

POST /loginGET /logoutを定義します。

index.rb
post '/login' do
  if user = user_fetch(params['name'], params['pass'])
    cookies[:session_id] = SecureRandom.uuid if cookies[:session_id].nil? || cookies[:session_id] == ""
    session_save(cookies[:session_id], { name: user[:name] })
  end
  redirect back
end

get '/logout' do
  cookies[:session_id] = nil
  redirect back
end

GET /POST /を修正します。

index.rb
 get '/' do
+  name = session_fetch(cookies[:session_id])&.[]("name")
   chats = chats_fetch
   erb :index, locals: {
+    name: name,
     chats: chats.map{ |chat| add_suffix(chat) }
   }
 end

 post '/' do
-  chat_push(params['content'])
+  name = session_fetch(cookies[:session_id])&.[]("name")
+  chat_push(params['content'], name)
   redirect back
 end

Viewのフォーム部分を書き換え、ログインしていない時はログインフォームが、ログイン後には投稿フォームが表示されるようにします。

vieqs/index.erb
<% if name %>
  <p>こんにちは<%= name %>さん</p>
  <a href="/logout">ログアウト</a>
  <form action="/" method="post">
    <input name="content" placeholder="投稿" />
    <button type="submit">送信</button>
  </form>
<% else %>
  <form action="login" method="post">
    <input name="name" placeholder="ユーザ名">
    <input name="pass" placeholder="パスワード">
    <button type="submit">ログイン</button>
  </form>
<% end %>

http://localhost:4567/initialize にアクセスし、adminユーザでログインできたら成功です。

Appの複数台化

docker-compose.ymlを下記のように修正します。
appを2つにして、あらたにWebサーバとなるNginxのコンテナを追加します。
Sinatraの4567ポートを閉じて、Nignx用の8080ポートを開けます。

Nginxは https://hub.docker.com/_/nginx から好きなバージョンを取ってきて使います。

docker-compose.yml
 version: '3'
 services:
-  app:
+  app1: &app
     build: .
     volumes:
       - .:/app
       - /app/vendor/bundle
-    ports:
-      - 127.0.0.1:4567:4567
     environment:
       - MYSQL_HOST=db
       - MYSQL_USER=root
       - MYSQL_PASS=secret
       - MYSQL_DATABASE=nopochat_development
+  app2:
+    <<: *app
+  web:
+    image: nginx:1.19-alpine
+    volumes:
+      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
+    ports:
+      - 127.0.0.1:8080:80
(以下略)

Nginxの設定ファイルを配置します。app1app2を振り分けるようにします。

nginx/default.conf
upstream apps {
  server app1:4567;
  server app2:4567;
}

server {
  listen 80;
  proxy_set_header Host $host:8080;

  location / {
    proxy_pass http://apps;
  }
}

http://localhost:8080/ にアクセスします。もしログイン処理がうまくできていないと、アクセスするたびにセッションが切り替わってログアウトしたりします。

RubyからRustを呼び出す

序盤に挙げた、語尾に「も」を付与する処理

def add_suffix(chat)
  { **chat, content: "#{chat[:content]}も" }
end

これをRustで書いてRubyから呼び出してみます。

https://hub.docker.com/_/rust から好きなバージョンを取ってきます。今回はrust:1.46-slimを使います。
下記コマンドで、rust_libというディレクトリにRustのプロジェクトを作成します。

$ docker run \
  --rm \
  --volume $(pwd):/app \
  --workdir /app \
  --env USER=root \
  rust:1.46-slim cargo new rust_lib --lib
$ cd rust_lib 

お好みでGitHubの.gitignoreから取ってきてセットします。

$ curl https://raw.githubusercontent.com/github/gitignore/master/Rust.gitignore -o .gitignore

Cargoでlibc crateを追加、crate-typeを"dylib"に指定します。

rust_lib/Cargo.toml
[dependencies]
libc = "0.2.77"

[lib]
name = "rust_lib"
crate-type = ["dylib"]

Rustで処理を書いていきます。

rust_lib/src/lib.rs
extern crate libc;
use libc::*;
use std::ffi::{CStr, CString};

#[no_mangle]
pub extern fn add_suffix(s: *const c_char) -> CString {
    let not_c_s = unsafe { CStr::from_ptr(s) }.to_str().unwrap();
    let not_c_message = format!("{}も", not_c_s);
    CString::new(not_c_message).unwrap()
}

ビルドします。

$ docker run \
  --rm \
  --volume $(pwd):/app \
  --workdir /app \
  rust:1.46-slim cargo build

rust_lib/target/release/librust_lib.soがビルドされていれば成功です。

$ nm target/release/librust_lib.so | grep add_suffix
00000000000502c0 T add_suffix

Ruby FFI Gemを使って、RubyからRustを呼び出す処理を書いていきます。

$ docker-compose run --rm app1 bundle add ffi
index.rb
 require 'sinatra'
 require 'sinatra/reloader' if settings.development?
 require 'sinatra/cookies'
 require 'mysql2'
+require 'ffi'

extend FFI::Libraryして、先程のrust_lib/target/release/librust_lib.soを読み込んで使います。FFIのwikiを参考に、引数と返り値の型を指定します。

index.rb
# Rustから呼び出すモジュールの設定
module RustLib
  extend FFI::Library
  ffi_lib('rust_lib/target/release/librust_lib.so')
  attach_function(:add_suffix, [:string], :string)
end

GET /を修正します。

index.rb
 get '/' do
   name = session_fetch(cookies[:session_id])&.[]("name")
   chats = chats_fetch
   erb :index, locals: {
     name: name,
-    chats: chats.map{ |chat| add_suffix(chat) }
+    chats: chats.map{ |chat| { **chat, content: RustLib::add_suffix(chat[:content]).force_encoding("UTF-8") } }
   }
 end

以上です。完成品は下記になります。

https://github.com/s2terminal/sinatra-chat

参考

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

雰囲気でコンテナを立ち上げないために。あらためて学ぶDocker Composeの基礎

はじめに:qiitan:

この記事は、Docker-compose.ymlを書いたり、docker-composeコマンドを使ってアプリケーションを開発したり運用したことがあるが、

——全然わからない、俺は雰囲気で複数コンテナを立ち上げているーー

という方(主に自分)に向けて、あらためてDocker Composeがどういうものかを、簡単に説明した記事です。

この記事のゴール?

  • なぜ複数コンテナの管理ツールが必要になるのかを把握する
  • 複数コンテナ実行管理ツールであるDocker Composeの基礎を理解する

複数コンテナ管理の基礎を学ぶ?

1. なぜ複数コンテナの管理ツールが必要になるのか

1.1. 複数コンテナを運用するパターン

現在の一般的な構成である三層アーキテクチャのWebアプリケーションをDockerを使って開発・運用することを例として挙げます。

Screenshot 2020-09-13 18.30.25.png

プロセスとしては、Webアプリケーションデータベースの3つのプロセスが考えられます。これを1コンテナで実現するのは、(できるにはできるみたいですが)あまり良い構成とは言えません。
スケール・管理がしにくく、プロセスが一つでも落ちるとコンテナが落ちてしまうなど、リスクが高いためです。

Screenshot 2020-09-13 19.17.30.png

なので、現在は1コンテナ=1プロセスとして、それぞれの役割ごとにコンテナを立てるのが一般的になっています。必要に応じてコンテナを追加したり差し替えができるため、スケールや変更が容易になります。
Screenshot 2020-09-13 19.23.56.png

1.2. 管理ツールを使わずに複数コンテナを動かすと...?

ここで、コンテナの起動・管理方法について考えます。1コンテナ=1プロセスなのですから、例えば今回の環境を立ち上げる際には、計3回docker build & docker runをすることになります。これは、環境の構成コンテナが増えるごとにコマンドを打つ回数が増えていくことを意味します。また、ポートマッピングの設定など、それぞれのコンテナを起動する際にオプションでいちいち指定しなければなりません。シェルスクリプト化するにしても、コンテナの立ち上げ順序等もあるため、環境の管理は面倒でしょう。

例えばのイメージ
$ docker run --name mysql #この後にvolumesなどのいろいろなオプションがつく
$ docker run --name nginx
$ docker run --name rails
$ #...コンテナの数だけ回数は増える

こうなると、環境を構築するのが面倒になりますし、手動でそれぞれのコンテナを運用することになります。単純に管理・維持が大変になり、"環境構築の手軽さ"というDockerの特徴を潰すことになります。

2. Docker Compose とは?

image.png
ここで、Docker Composeの登場です。
Docker Compose とは、複数コンテナで構成されるプロジェクトの起動・停止などの手順を、yamlファイルに定義した設定内容に基づいて自動的にしてくれるツールです。

docker-compose.yml(適当に作った例)
version: '3'
services:
  db:
    image: postgres:alpine
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    networks:
      - sampleapp

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./web/app/
    ports:
      - '8080:8080'
    depends_on:
      - db
    networks:
      - sampleapp

networks:
  sampleapp:
    external: true

各プロセスのDockerfileを用意した上で、上記のようにdocker-compose.ymlにプロジェクトの設定内容を記述しておき、

$ docker-compose up

とすると、簡単に環境を立ち上げることができます。

2.1. docker-compose.yml で定義する内容

さて、docker-compose.ymlでは、どのようなことが設定できるのでしょうか?
Compose ファイル・リファレンスによると、

Compose ファイルは YAML ファイルであり、 サービス(services) 、 ネットワーク(networks) 、 ボリューム(volumes) を定義します。

とあります。
うーん、なんとなく今まで雰囲気でdocker-compose.ymlに書いてきたから、よくわかっていない...(心の声)
ということで、それぞれについてあらためて確認していきたいと思います。

2.1.1. services

Screenshot 2020-09-17 19.39.36.png

servicesには、プロジェクトを構成する各コンテナの情報を定義します。ここには、各コンテナのdocker run時に指定していたオプションを記述することができます。ポートマッピングの設定や、後述するvolumesやnetworksの設定などもここに記述することで、docker-compose upでのコンテナ一括起動時に反映してくれるようになります。

2.1.2. volumes

Screenshot 2020-09-24 17.44.49.png

続いて、volumesに記述する内容です。
起動したコンテナで扱うデータは、コンテナが削除されると一緒に消えてしまいます。また、コンテナを複数起動した際、そのままではお互いのファイルシステムを直接参照できません。

ですが、コンテナにはボリューム(volume)と呼ばれる、ディレクトリやファイルをマウントできる領域があり(実体はホスト上に自動生成されるディレクトリ /var/lib/docker/volumes)、異なるコンテナでも、dockerコマンドのオプションで同じvolumeを指定することで、データの共有が可能になります。

Docker Compose では、その設定も各コンテナ起動時にいちいちオプションをつける代わりに、docker-compose.ymlvolumesに定義することで、docker-compose upでのコンテナ一括起動時に反映してくれるようになります。

2.1.3. networks

Screenshot 2020-09-24 17.45.29.png

最後にnetworksに定義する内容についてです。
今回の例では、アプリケーションを構成するWeb・アプリケーション・DBの各コンテナを通信させる必要がありました。
ここで、Dockerには、3つの標準ネットワークモデルがあり、設定によってホスト側のネットワークに直接接続したり、コンテナ間を通信させることができます(わかりやすい参考記事)。

特に何も設定せずにコンテナを複数起動させている場合、各コンテナはIPアドレスを指定して通信することは可能ですが、コンテナ名を指定して通信することはできません。コンテナ名で通信させるにはユーザー独自のDockerネットワークを設定する必要があります。

その設定も、本来ならdocker networkコマンドでいちいち指定する必要がありますが、Docker Composeではdocker-compose.ymlnetworksに定義することで、docker-compose upでのコンテナ一括起動時に反映してくれるようになります。

まとめ:stock:

  • 管理ツールを使わずに、複数コンテナ構成プロジェクトを運用しようとすると、各コンテナ実行時にいちいちコマンドを走らせる&オプションで設定する必要があるため、環境構築や環境の維持・再現が大変

  • Docker Composeを使うと、docker-compose.ymlに各コンテナの情報をまとめて定義しておくことができ、1コマンドで環境が構築できる

  • Docker Composeによる管理は、特にnetworkvolumeが複雑な場合に真価を発揮する

引用・参考記事

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

チームで共有するための『Rails 6 x MySQL 8』Docker環境構築手順

今回はRails 6とMySQL 8を組み合わせたWebアプリケーションのDocker環境を構築する手順について紹介します。

Rails 6からwebpackerが標準でインストールされるようになったり、MySQL 8からユーザー認証の方式が変わったりと環境構築でつまる部分がいろいろとあったため参考になればと思います。

複数人でもスムーズに開発ができるようにするためリモートリポジトリからcloneしてきたらdocker-compose upするだけでアプリケーションが立ち上がるという環境をゴールにします。

各種バージョンは以下の通りです。

  • Ruby on Rails: 6.0.3.2
  • Ruby: 2.7.1
  • MySQL: 8.0.21

実行環境はDocker Desktop for Mac(バージョン 2.3.0.4)を利用しています。

Railsアプリケーションの準備

ディレクトリの作成・移動をします。
今回作成するRailsアプリケーション名はsample_appとします。

$ mkdir sample_app && cd $_

rubyイメージを利用してローカル環境にGemfileを作成します。
-vはバインドマウント(ホストとコンテナのディレクトリの同期)のオプションです。

ホストのカレントディレクトリとコンテナのワークディレクトリを同期させることで、コンテナ上で作成されるファイルをホストに配置します。

$ docker run --rm -v `pwd`:/sample_app -w /sample_app ruby:2.7.1 bundle init

作成されたGemfileのgem "rails"の部分をアンコメントし、railsのバージョンを指定します。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
- # gem "rails"
+ gem "rails", '~> 6.0.3.2'

rails newを実行するDocker環境を構築するためDockerfileを作成します。

Rails 6ではアプリケーションを作成する際にrails webpacker:installも実行されるのでyarnのインストールを忘れずにしましょう。
今回はnpmを利用してyarnをインストールします。

Dockerfile
FROM ruby:2.7.1

RUN apt-get update -qq && \
    apt-get install -y nodejs \
                       npm && \
    npm install -g yarn

# 作業ディレクトリを/sample_appに指定
WORKDIR /sample_app

# ローカルのGemfileをDokcerにコピー
COPY Gemfile /sample_app/Gemfile

# /sample_appディレクトリ上でbundle install
RUN bundle install

Dockerfileをビルドして作成されたイメージを利用してコンテナを起動し、コンテナ上でrails newをします。

$ docker build -t sample_app .

$ docker run --rm -v `pwd`:/sample_app sample_app rails new . –skip-bundle --database=mysql

docker-compose.ymlを作成します。

今回は説明を簡略化するため、rootユーザーでMySQLに接続しています。
一般ユーザーで接続をする場合はMySQLのイメージに対して以下の環境変数を設定する必要があります。

環境変数 内容
MYSQL_USER ユーザー名
MYSQL_PASSWORD ユーザーパスワード

データベースの情報はmysql_dataという名前付きボリュームを作成して永続化します。

docker-compose.yml
version: '3'
services:
  web: # Ruby on Railsが起動するコンテナ
    build: .
    ports:
      - '3000:3000' # localhostの3000ポートでアクセスできるようにする
    volumes:
      - .:/sample_app # アプリケーションファイルの同期
    depends_on:
      - db
    command: ["rails", "server", "-b", "0.0.0.0"]
  db: # MySQLが起動するコンテナ
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql # データの永続化
    command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする。
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'
volumes:
  mysql_data: # データボリュームの登録

上記のdocker-compose.ymlの補足説明をします。

MySQLのDockerイメージではMYSQL_DATABASEに設定された名前のデータベースを作成してくれます。
Ruby on Railsのdevelopment環境では[アプリケーション名]_developmentというデータベースを利用するため、sample_app_developmentMYSQL_DATABASEに登録しています。
これでrails db:createを実行しなくてもdevelopment環境のデータベースを用意できます。

MySQL 8からは認証方式がmysql_native_passwordからcaching_sha2_passwordに変更されました。
MySQL 8標準のcaching_sha2_passwordの認証方式だとデータベースへ接続できず、Railsアプリケーション起動時に以下のようなエラーメッセージが表示されてしまいます。

Mysql2::Error::ConnectionError

Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory

スクリーンショット 2020-09-15 9.15.45.png

そこで、--default-authentication-plugin=mysql_native_passwordで以前のmysql_native_passwordの認証方式を利用するようにしています。

次にRuby on Railsのデータベース接続設定を行います。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: pass # (今回はrootなので)MYSQL_ROOT_PASSWORDと一致させる
  host: db # データベースのコンテナ名を設定する

Railsアプリケーションの起動確認

Ruby on Rails 6とMySQL 8を組み合わせたDocker環境ができあがったので起動をしてみます。

$ docker-compose up

localhost:3000にアクセスして以下の画面が表示されればOKです。

スクリーンショット 2020-09-15 9.23.43.png

データベースのデータの永続化についても確認をしてみます。
例としてscaffoldeventに関する機能を作成します。

$ docker-compose exec web rails g scaffold event title:string
$ docker-compose exec web rails db:migrate

localhost:3000/events/newにアクセスし、例として『サンプルイベント』というレコードを登録してみます。

コンテナを削除・起動してもデータが残っていればOKです。

$ docker-compose down
$ docker-compose up

$ docker-compose exec web rails c
> Event.last.title
=> "サンプルイベント"

テスト環境用のデータベースの作成

MYSQL_DATABASEを利用してdevelopment環境のデータベースを作成することでdb:createを実行することなくRailsアプリケーションを起動できるようにしました。

しかし、テストコードで利用するtest環境のデータベース([アプリケーション名]_test)が作られていないためこれだけでは開発環境としては不十分です。

MySQLのDockerイメージでは/docker-entrypoint-initdb.dにスクリプト(.sql.sh.sql.gz)を配置しておくとコンテナ起動時に実行してくれるという機能があります。1
この機能を活用してtest環境用のデータベースを作成します。

スクリプトはアルファベット順で実行されるため、スクリプト間に依存関係がある場合はファイル名も意識してつけるようにしておきましょう。

$ mkdir docker-entrypoint-initdb.d && cd $_
$ vim 00_create.sql
00_create.sql
-- test環境用のデータベースを作成する
CREATE DATABASE sample_app_test;

なお、一般ユーザーのデータベースアクセス権はMYSQL_DATABASEに設定したデータベースのみとなっています。
ですので、一般ユーザーでデータベースに接続する場合はデータベースの作成だけでなく、以下のようなアクセス権の付与も実行する必要があります。

01_grant.sql
-- webappという名前の一般ユーザーを利用する場合
GRANT ALL ON `sample_app_test`.* TO webapp@'%';

作成したスクリプトをコンテナ上で読み取れるようにするためバインドマウントを追加します。

docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
    command: ["rails", "server", "-b", "0.0.0.0"]
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
+     - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'
volumes:
  mysql_data:

データベースが自動作成されることを確認

dbコンテナを削除・起動し、自動でデータベースが作られるか確認をしてみます。
[アプリケーション名]_development[アプリケーション名]_testのデータベースが存在していればOKです。

# データベースの永続化情報も削除して0からデータベースを作成する
$ docker-compose down --volumes

$ docker-compose up

$ docker-compose exec db mysql -uroot -ppass -D sample_app_development

mysql> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sample_app_development |
| sample_app_test        | ← test用のdbが作成されている
| sys                    |
+------------------------+
6 rows in set (0.00 sec)

Railsアプリケーションからも正常に接続できます。

$ docker-compose exec web rails c

> ENV['RAILS_ENV']
=> "development

$ docker-compose exec web rails c -e test

> ENV['RAILS_ENV']
=> "test"

『clone → docker-compose up』だけで環境が立ち上がるようにする

DockerでRailsの開発環境を構築する方法としてよく見かけるのが以下のようなパターンです。

# コンテナを立ち上げる
$ docker-compose up

# データベースの作成
$ docker-compose exec web rails db:create

# テーブルのマイグレーション
$ docker-compose exec web rails db:migrate

Railsアプリケーションとデータベースをdocker-composeで連携させ、アプリケーションで利用するデータベースはコンテナ上で構築するという方法です。

上記の方法でも問題はないのですが、この場合だと初回起動時にデータベースとテーブルの作成をコンテナ上で手動実行する手間がかかります。

複数人でDocker環境を共有する時のことを考えると、リモートリポジトリからアプリケーションファイルをcloneしてきたらdocker-compose upをするだけで環境が立ち上がるのが理想です。

共有メンバーに対して「初回起動時はデータベースの作成をコンテナ上で実施してね」とわざわざ伝えないといけない状況はできれば避けたいです。

ここからはリモートリポジトリからcloneしたらdocker-compose upするだけでRailsアプリケーションが起動できるようにするための設定を行います。

yarn installをコンテナ起動時に実行する

開発環境ではバインドマウト(ホストとコンテナのディレクトリの同期)を利用したソースコードの同期がよく利用されます。

リモートリポジトリからcloneしてきたアプリケーションファイルはpackage.jsonはありますが、node_modulesディレクトリはありません。
そのため、cloneしたあとdocker-compose upをするとnode_modulesがない状態のディレクトリがバインドマウントされます。その結果yarn installの実行を促すエラーが発生し、アプリケーションが起動できません。

========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================

To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).

yarn check v1.22.5
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.

これを防ぐにはコンテナ起動時にyarn installを実行する必要があります。
たとえば以下のようにすることでコンテナ起動時にyarn installを実行できます。

docker-compose.yml
version: '3'
services:
  web:
    build: .
+   command: ["./start.sh"]
-   command: ["rails", "server", "-b", "0.0.0.0"]
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
  db:
    image: mysql:8.0.21
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'
start.sh
#!/bin/bash -eu
yarn
rails server -b '0.0.0.0'

シェルのパーミッションを変更します。

$ chmod 755 start.sh

コンテナを起動する際のコマンドでシェルを呼び出し、シェルの中でyarn installを実行するようにしています。
yarn install実行後、rails serverでRailsアプリケーションが起動するため先ほどのエラーが解消されます。

マイグレーションを自動実行できるようにする

テーブルのマイグレーションもyarn installと同様、コンテナ起動時に呼び出されるシェルスクリプトで実行するようにします。

start.sh
#!/bin/bash -eu
yarn
+ rails db:migrate
rails server -b '0.0.0.0'

しかし、docker-compose.ymldepends_onを利用することでコンテナの起動順は制御できますが、コンテナの起動を待つということはできないため2、dbコンテナの起動準備が終わる前にdb:migrateが実行されるとマイグレーションが正常に行われません。

マイグレーションの自動化をするにはdbコンテナの起動を待ってからdb:migrateが実行されるようにする必要があります。

データベースの準備を待つ方法にはいくつかありますが、今回はwait-for-itを利用する方法を紹介します。

wait-for-it.shをカレントディレクトリに配置し、docker-compose.ymlを以下のようにするとデータベースの起動を待ってからスクリプトが実行されます。

docker-compose.yml
version: '3'
services:
  web:
    build: .
-   command: [""./start.sh"]
+   command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
  db:
    image: mysql:8.0.21
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'

まとめ

以上でRails 6 + MySQL 8のDocker環境の構築手順の紹介を終わります。

コンテナ起動時にシェルを実行することで、複数人でDocker環境を利用する場合でもスムーズに開発環境を構築できるようにしました。
なお、今回はコンテナ起動時にシェルを呼び出すことでyarn installdb:migrationを確実に実行するようにしましたが、コンテナ起動時のコマンドの制御はEntrykitと呼ばれるツールでも行えます。

EntrykitについてはRailsのDocker環境にEntrykitを導入し、bundle installを自動実行させる方法で紹介していますので、興味のある方はご覧になってください。

  • 今回のまとめ
    • Rails 6からはyarnのインストールも事前に行う必要がある
    • MySQL 8の認証エラーが発生した場合は標準の認証に戻すことで解決する
    • コンテナ起動時にシェルを実行することでテーブル作成等の自動化が可能になる

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考記事

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

AthrillのDockerコンテナ最小化手順

はじめに

以前書いたメモが出てきたので、記事にしときます。

AthrillをDockerで動かす方法は

https://qiita.com/kanetugu2018/items/f1368a6da7bdc773cfd9

で紹介されていますが、折角なのでできるだけ小さくしてみたいと思います。
箱庭で数匹・数十匹のAthrillを同時に動かしたりできると面白そうですよね。

今回は、Dockerのscratchから構成したいと思います。

Athrillのstaticビルド

athrillを普通にビルドするとダイナミックリンクライブラリを利用するので、
何も入っていない環境で実行しようとするとコケます。

このため、スタティックにビルドしなおします。

Makefileをこんな感じに変えて、

/trunk/src/build/target/linux_v850e2m/Makefile
28: $(GCC) -O3 $(LFLAGS) $(AROBJS) -o $(TARGET)  $(LIBS) -static

再ビルドします。

$ make clean; make

Dockerfile

docker用にフォルダ作って、静的ビルドしたathrillと
利用するバイナリ、aspや設定ファイル類を集めておき、そこでDockerfile作ります。

FROM scratch

COPY athrill2 .
COPY asp .
COPY memory.txt .
COPY device_config.txt .

CMD ["./athrill2", "-i", "-m", "memory.txt", "-d", "device_config.txt", "asp"]

今更気づきましたら、CMDでオプション渡す必要ないですね。athrillだけ叩くようにして、
引数で渡す方が素直そうです。

コンテナ作成

docker build -t athrill .

実行

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

私がよく使うDockerコマンド一覧(コンテナ操作編)

はじめに

色々操作しているとDockerの公式マニュアル見たして覚える努力をしていたのですが、
色々なことを手を出すようになり段々覚えていられなくなってきたので、
よく使っているコマンドを一覧化して書き出してみました。

コンテナ作成操作関連

以下にいくつかよく使うものを上げましたがrun optionはもっと色々あるので、
DockerのURLを参照してください。

1) ホスト再起動や意図しないコンテナの停止の時に、自動的に再起動してくれるオプション

docker run --restart=always

2) コンテナのボリュームをホストにマウント

docker run -v [ホスト側のボリュームパス]:[コンテナ内のパス] [起動したいコンテナイメージ名]

3) コンテナに外部からアクセス(ポートフォワーディング)
複数指定することができます

docker run -p [外部ポート]:[コンテナポート]

コンテナへのログインやコマンド実行

1) コンテナへのログイン

docker exec -it [コンテナID or コンテナ名] /bin/bash

2) コンテナ内でのコマンド実行

docker exec [コンテナID or コンテナ名] [実行したいコマンド]

コンテナのステータス確認操作

1) 現在起動しているコンテナの一覧を表示
起動しているかどうかはSTATUSの欄がUPとなっているかを確認する

docker ps

2) 停止/起動しているコンテナの一覧を表示
※-aは--allの略

docker ps -a

コンテナのログ確認

1) コンテナのログをcatで表示させるようなものです

docker logs [コンテナ名 or コンテナID]

2) ログを出力する際に時刻を付加します

docker logs -t [コンテナ名 or コンテナID]

3) ログをリアルタイムに表示し続ます

docker logs -f [コンテナ名 or コンテナID]

4) ログの最後から指定した行数を表示

docker logs --tail=[表示行数] [コンテナ名 or コンテナID]

コンテナの削除操作

1) 停止しているコンテナを削除

docker rm [コンテナ名 or コンテナID]

2) 起動しているコンテナを削除
※停止してから削除が本来は望ましいです。

docker rm -rf [コンテナ名 or コンテナID]

3) 停止しているコンテナ全てを削除
-aで停止しているコンテナを全て抽出し、その中から-qでコンテナIDを抽出し削除
※起動しているコンテナについてはエラーがでて削除できないので、
 その場合には起動しているコンテナを停止してから実施しましょう。

docker rm $(docker ps -q -a)

コンテナの停止/起動/再起動操作

1) 起動している特定のコンテナを停止

docker stop [コンテナ名 or コンテナID]

2) 停止している特定のコンテナを起動

docekr start [コンテナ名 or コンテナID]

3) 特定のコンテナを再起動

docekr restart [コンテナ名 or コンテナID]

4) 全てのコンテナを停止
-qでコンテナIDを抽出してコンテナを停止

docker stop $(docker ps -q)

5) 全てのコンテナを起動
-qでコンテナIDを抽出してコンテナを起動

docker start $(docker ps -q)

Docker操作 その他

1) 停止中のコンテナ、使用されていないネットワーク、使われていないイメージなどなど全て削除してくれる
ただし一括削除されるので実行する際には気を付けて下さい。。。

docker system purne

2) 作成したコンテナもしくはイメージ名の詳細情報を表示します

docker inspect [コンテナ名 or コンテナID or イメージID]

3) コンテナ名を変更

docker rename [変更前のコンテナ名] [変更後のコンテナ名]

最後に

今回はDockerの主にコンテナの作成~削除をメインに触れました。
次記載する時はもう少し踏み込んだことを書いていきたいと思います。

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

OpenVINO の demo を (Google Colab|Docker) でそのまま使うだけ

Intel 入ってたら動く OpenVINO の Toolkit についてくる interactive_face_detection_demo が、そのまま動かすだけでそこそこ使えて嬉しかったのでメモです。以下 OpenVINO 2020.4 での情報になります。

openvino_demo_input.gif

openvino_demo_output.gif

Google Colab を利用する場合

Google Colab はIntel CPU を利用しているようなので、Colab 上で動かせます。

実際に動かした Notebook はこちらです。

インストールはLinux 用のインストール手順を実施していくだけ、demo のビルド・実行は下記 Docker を利用する部分と同じなので、解説は割愛しています。

ただ、Colab を起動する度にインストールを待つのが面倒なのと、特性上出力ファイルがかなり大きくなるのでファイルのダウンロードが面倒なのと、そもそも Colab の CPU はそんなに早くないことが微妙なところです。Intel 入ってる環境があるなら、ローカルにファイルを直接書き込める 以下 Docker を使った方法が楽かも知れません。

ローカルに直接環境を構築する場合

既存環境と干渉するらしい記事を見たのでなんとなく避けました。インストールの項を実施していけば問題ないと思います。

Docker を利用する場合

docker run -v /c/Users/${USER}/Downloads:/Downloads -u root -it --rm openvino/ubuntu18_dev:2020.4

demo を使いたいので demo 入りの _dev を使います
ファイルをやり取りするのでダウンロードディレクトリをマウントして、インストールファイルをイジりたいので root になります。自分は Windows 10 上で動かしているので CPU only しか対応してなかったですが、対応環境では docker にオプションを渡すことで GPU などにも対応するようです。

以下コンテナ内で作業

cd ${INTEL_CVSDK_DIR}/inference_engine/demos/
sed -i 's/*)/interactive_face_detection_demo)/g' CMakeLists.txt
./build_demos.sh

そのままだと他 demo のビルドも待たないといけないので、使いたい interactive_face_detection_demo だけに絞っています。他 demo も使ってみたい方は sed を抜いてください。

${INTEL_CVSDK_DIR}/deployment_tools/tools/model_downloader/downloader.py \
  --name face-detection-adas-0001,age-gender-recognition-retail-0013,head-pose-estimation-adas-0001,emotions-recognition-retail-0003,facial-landmarks-35-adas-0002 \
  --output_dir /content/model/ \
  --precisions FP32

interactive_face_detection_demo で使いたい学習済モデルを、付属のダウンローダーで落としてきます。face-detection などモデルがいくつもあるやつはどれを使えばいいのか迷いますが、適当に変えたら死ぬほど遅くなったりした(100倍ぐらい)ので注意です。とりあえず上記はドキュメントで指定があったものです。

/root/omz_demos_build/intel64/Release/interactive_face_detection_demo \
  -i /Downloads/input.mp4 \
  -m /content/model/intel/face-detection-adas-0001/FP32/face-detection-adas-0001.xml \
  -m_ag /content/model/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013.xml \
  -m_hp /content/model/intel/head-pose-estimation-adas-0001/FP32/head-pose-estimation-adas-0001.xml \
  -m_em /content/model/intel/emotions-recognition-retail-0003/FP32/emotions-recognition-retail-0003.xml \
  -m_lm /content/model/intel/facial-landmarks-35-adas-0002/FP32/facial-landmarks-35-adas-0002.xml \
  -no_show \
  -no_wait \
  -async \
  -o /Downloads/output.mp4

オプションは他にもありますが、とりあえず上記で動画を変換できました。

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

Django 認証機能がうまく反映されない

はじめに

この記事ではDjango + Dockerでユーザー認証機能を盛り込む際に出たエラーの対処法について書いていきます。

ModuleNotFoundError: No module named 'allauth'

このエラー文にめちゃくちゃ悩まされました。
どうしたら良いのかわからずあきらめかけていましたが、対処法はいとも簡単でした...。

まず「Docker」ファイルに次のように追記してください。

Dockerfile
RUN pip install django-allauth

次に「requirements.txt」にも追記していきます。

requirements.txt
django-allauth>=0.32.0

最後にターミナルに次のように打ち込みます。

terminal
$ docker-compose build

最後にお決まりの作業をしていきます。

terminal
$ docker-compose run --rm web python3 manage.py makemigrations

$ docker-compose run --rm web python3 manage.py migrate

$ docker-compose up

これで完了です。

最後に

このエラーに何時間もかけてしまったので、同じようにつまずいている人の助けになれば幸いです。

コマンドについてはこちらにまとめておいたので、参考になれば幸いです。

Django + Dockerコマンド

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

ローカル(ホストOS)のJavaプログラムからDockerコンテナ上のMySQLにアクセスする

はじめに

自分の技術スタックがあまりにもレガシーで情けなく感じ
レガシーおじさんを脱却し、技術者として再出発したく
勉強を始めました。宜しくお願いします?

ということでまずはDockerを勉強することに。

構築するもの(今回のゴール)

  • 構成図
    MySQL_Dockerトレーニング1.png

  • ソフトウェア情報
    OS:Windows10
    Docker:19.03.12
    Program:Java11
    MySQL:8.0.21
    Adminer:latest
    WebBrowser:なんでも

とりあえず今回はホストOS上の自前のJavaプログラムから
コンテナ上のDBにアクセスするところからチャレンジ。

構築作業

1. Dockerコンテナ作成

DockerHubのMySQLの説明ページにあるdocker-compose.ymlをちょっとアレンジして準備。
DockerHub MySQLページ

# Use root/example as user/password credentials
version: '3.1'

services:

  db:
    image: mysql:8.0.21 #変更
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: hoge #追加
      MYSQL_USER: hoge     #追加
      MYSQL_PASSWORD: hoge #追加
    ports:                 #追加
      - 3306:3306          #追加

  adminer:
    image: adminer:latest
    restart: always
    ports:
      - 8080:8080

#が付いてるところがアレンジ部分。
MySQLイメージはlatestを指定するとバージョンがころころ変わりそうな気がしたので8.0.21を指定。
今回はコンテナ外部からアクセスするため、
適当なDBの追加とポート3306を外部公開するように設定。

上記設定ファイルと同階層のフォルダで
docker-composeコマンドで起動。

docker-compose up

フォアグラウンド実行なのでずらずらログが出るのを見つつ、
別コンソールで立ち上がってるか確認

docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
0c5d267f22ee        mysql:8.0.21        "docker-entrypoint.s…"   26 seconds ago      Up 25 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   mysql_db_1
258f5885810d        adminer:latest      "entrypoint.sh docke…"   26 seconds ago      Up 25 seconds       0.0.0.0:8080->8080/tcp              mysql_adminer_1

(´・ω・`)おぉー

2. Adminerでテーブル、データ作成

AdminerとはPHPで作られたDB操作できるWebクライアントらしい。
MySQL、Adminerのコンテナが立ち上がってる状態で
http://localhost:8080/
にアクセスすると以下の画面が出るので
さっきのdocker-compose.ymlに記載したDB、ユーザ情報を入れる。
スクリーンショット 2020-09-16 025707.png

そしたらこんな感じの適当なテーブルと
スクリーンショット 2020-09-16 030143.png

こんな感じの適当なレコードを入れて、DBは完了。
スクリーンショット 2020-09-16 030441.png

3. Javaプログラム作成

まずgradleにMySQLドライバの依存関係を記載。

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'eclipse'

repositories {
    mavenCentral()}

dependencies {
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.21'
    testImplementation 'junit:junit:4.12'
}

そしてMySQLにSQLクエリを投げて結果を表示するクラスを作成。

public class DockerMySQLAccessor {

    public static void main(String[] args) {

        System.out.println("DockerコンテナのMySQLにアクセスするよ");

        try(Connection con = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/hoge?useSSL=false",
                "hoge", // UserId
                "hoge"  // Password
              )) {

            PreparedStatement pstmt = con.prepareStatement("select * from TEST_TBL");
            ResultSet rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.printf("ID:%d, HOGE:%s \n", rs.getInt("ID"), rs.getString("HOGE"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4. 実行

EclipseからさっきのJavaコードを実行。
スクリーンショット 2020-09-16 032059.png
さっき入れたレコードが取得できてる!
やったぜ。(´・ω・`)v

まとめ

とりあえずDockerを使って手軽に仮想的なDBサーバを立ち上げ、
ローカルのプログラムからアクセスすることができた。

今回はDocker触りということで
設定ほぼ丸写しでしかもいきなりdocker-composeの方を使ったけど
実際の運用ではコンテナ毎にDockerfile作ってイメージを作っていくのかな?
あと、プログラムもコンテナ上で実行し、コンテナ間で通信したいところ。
ここら辺はまた今度。

謝辞

Win10にDockerを入れるの大いに参考にさせて頂きました。感謝<(_ _)>
レガシーエンジニアによるDocker入門

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

AmazonLinux2のDockerコンテナにPython3を入れる

# docker run
docker run -it --rm amazonlinux bash

コンテナ内で、

# install
amazon-linux-extras install python3.8 -y

# run
python3.8

# venv
cd /tmp
python3.8 -m venv venv
source venv/bin/activate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeのdevcontainerでLaravel・Docker環境を構築する

概要

VSCodeのdevcontainerを使ってLaravel環境構築を解説する記事です。
使用ツールさえ用意すれば高速で環境構築でき、Dockerを使用しているのでチームでの統一された開発環境としても使用できます。
とりあえず動かしたい人はリポジトリリンクからどうぞ。

リポジトリ

https://github.com/naoyayamamoto/laravel-docker-sample
テンプレートリポジトリとして用意しているので、雛形としても使用できます。

使用ツール

以上のツールを使用します。
DockerVisual Studio Codeについてはインストールの詳細を省きます。
Visual Studio Code Remote - ContainersについてはMicrosoft謹製のVSCodeプラグインです。リンクよりVSCodeにインストールできます。

使用方法

リポジトリをVSCodeで開く

$ git clone https://github.com/naoyayamamoto/laravel-docker-sample
$ code laravel-docker-sample
# codeコマンドが入っていなければVSCodeでリポジトリを開いてください

devcontanerを使用してVSCodeを開く

  1. VSCodeにVisual Studio Code Remote - Containersが入っていれば左下にアイコンが追加されているので、それを押してダイアログを開くか、Show All Commandscmd + shift + Pでダイアログを開く。
  2. Remote-Containers: Reopen in Containerを実行すると環境の準備が始まります。
  3. 初回はDockerの準備を行うため時間がかかりますが、2回目以降は高速で立ち上がります。

アクセス確認

http://localhost:8000へアクセスでLaravel初期ページが表示されるはずです。

laravel-8-top.jpg

解説

devcontainerを開いた際に.devcontainer/docker-compose.ymlを元にDocker環境が構築されます。
以下のコンテナを使用しており、それぞれ解説していきます。

  • nginx:alpine (web)
  • mysql:8 (db)
  • VSCode用にカスタマイズしたphp:7-fpm (app)

nginx:alpine (web)

  • デフォルトイメージを使用
  • フロントとして常に起動するので、php artisan serveなしでアクセス可能
  • .devcontainer/docker/nginx/default.confを読み込ませ、php-fpmへ処理を流している

mysql:8 (db)

  • デフォルトイメージを使用
  • .devcontainer/docker/mysql/my.cnfを読み込ませている(日本語基本設定)
  • 環境変数MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' MYSQL_DATABASE: laravelを設定しているので、rootパスワードなし、初期テーブルlaravelが作成される

VSCode用にカスタマイズしたphp:7-fpm (app)

  • devcontainerを開いた際の初期接続コンテナ
  • .devcontainer/docker/php/Dockerfileを使用してビルド
  • composernodenpmyarnが使用可能
  • php-fpmをsockで実行するために.devcontainer/docker/php/php-fpm.d/zzz-www.confを読み込ませている
  • DB_HOST: dbを設定しているので、.envでの設定よりこちらが優先されてdbコンテナに接続されます

おまけ

本番環境用のDockerfile

本番を想定してビルドを行うDockerfileがリポジトリに追加しているので、カスタマイズの元として使用してください。
マルチステージビルドを使用して、jsのコンパイルとcomposerのインストールを先行して行い、最終イメージを極力小さくしています。
GithubActionsでビルドテストだけは通しています。

kubernetesで使用する場合

参考までにkubernetesで使用する場合の設定です。
nginxとphpを1PODとして動かすことを想定しています。

参考deployment.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  default.conf: |
    access_log /dev/stdout main;
    error_log /dev/stderr warn;
    server {
        server_tokens off;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        gzip on;
        gzip_http_version 1.0;
        gzip_disable "msie6";
        gzip_proxied any;
        gzip_min_length 1024;
        gzip_comp_level 6;
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
        open_file_cache max=100000 inactive=20s;
        open_file_cache_valid 30s;
        open_file_cache_min_uses 2;
        open_file_cache_errors on;

        listen 80;
        root /workspace/public;

        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options "nosniff";

        index index.html index.htm index.php;

        charset utf-8;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location = /favicon.ico { access_log off; log_not_found off; }
        location = /robots.txt  { access_log off; log_not_found off; }

        error_page 404 /index.php;

        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            include fastcgi_params;
        }

        location ~ /\.(?!well-known).* {
            deny all;
        }
    }
---
kind: Service
apiVersion: v1
metadata:
  name: laravel-service
spec:
  selector:
    app: laravel
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      name: laravel-http
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: laravel
  labels:
    app: laravel
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: laravel
  template:
    metadata:
      labels:
        app: laravel
    spec:
      containers:
        - name: app
          image: myimage
          volumeMounts:
            - mountPath: /var/run/php-fpm
              name: php-fpm-socket
            - mountPath: /shared
              name: public-contents
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "cp -aT /workspace/public /shared"]
        - name: web
          image: nginx:alpine
          ports:
            - containerPort: 80
          volumeMounts:
            - mountPath: /var/run/php-fpm
              name: php-fpm-socket
            - mountPath: /etc/nginx/conf.d
              name: nginx-conf
            - mountPath: /workspace/public
              name: public-contents
      volumes:
        - name: php-fpm-socket
          emptyDir: {}
        - name: public-contents
          emptyDir: {}
        - name: nginx-conf
          configMap:
            name: nginx-conf
            items:
              - key: default.conf
                path: default.conf

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