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

DockerCompose で Gatsby環境構築

やりたいこと

Gatsbyの環境を作りたい
Dockerで...!

作り方

環境

Mac
Docker for Mac

  1. 作業フォルダにdocker-compose.ymlDockerfileを作成します

  2. Dockerfileの中身を下記のようにします

title=Dockerfile
FROM node:12.14.1

WORKDIR /home/node/app
RUN npm install -g gatsby-cli
EXPOSE 8000

nodeのLTSのイメージを使って、gatsby cliをインストールしているだけです
alpine使ってないのは私がコマンドをまだ理解できていないからです...
EXPOSEはコンテナのポートを公開しているだけなので、このままではアクセスはできませんが
次で解決するので進めます

3. docker-compose.ymlを下記のようにします

title=docker-compose.yml
version: "3.7"
services:
  gatsby:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: gatsby
    ports:
      - "8000:8000"
    volumes:
      - .:/home/node/app
    environment:
      - NODE_ENV=development
    stdin_open: true

解説としては

build:
      context: .
      dockerfile: Dockerfile

カレントディレクトリのDockerfileをビルドします

container_name: gatsby
コンテナ名をgatsbyにします

ports:
      - "8000:8000"

上記で、ホスト(Mac)とゲスト(docker)のポートをマッピング(関連付け)しています

volumes:
      - .:/home/node/app

volumes - volume(データの永続化領域)の定義
volume とは、コンテナのライフサイクルが終了した後でもデータを保管しておけるデータ領域です。
ホスト側のディレクトリを volume としてコンテナ内にマウントできる。
本機能はホストとコンテナ間でファイルを受け渡すときに利用できる。
参考:オブジェクトの広場

stdin_openはコンテナの標準入力をオンにします
getsby new 〇〇とかするのでオンにしております

4. ビルドしましょう! docker-composer up -d --build(ビルドとデーモンでの立ち上げ)
2回目以降はdocker-compose up -dです

5. docker exec -it gatsby bashでコンテナ内に入れます

6. コンテナに入ったら、gatsby new blog(別の名前でも可)を入力し、gatsbyのサイトを立ち上げます
別のテーマ使う場合は公式参考に
https://www.gatsbyjs.org/docs/themes/

7. gatsby developでビルドして、localhostを確認して、表示されていれば成功

お疲れさまでした

なお、おそすぎて使い物にならなかった...


ブログ始めました

https://c-blog.collapse-natsu.com/

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

Docker導入時のエラー(You must use Bundler 2 or greater with this lockfile.)

はじめに

dockerを既存のアプリに導入された際に発生したエラーとその解決法を今回はまとめさせていただきます。

 エラーの内容

docker file を作成docker-compose.ymlを作成した後にdocker-compose build コマンドをするとなぜか 

You must use Bundler 2 or greater with this lockfile.

という内容のエラーが発生しました。

解決法

ネットの記事を参考にしたところ

RUN gem install bundler 
RUN bundle install

とRUN bundle installの前に RUN gem install bundler を記述すれば治ると買いてありましたが、既に私は記述していました。

調べていくうちにruby2.5.1の場合はこのようなバグが発生するといった記事を見つけ rubyのversionを2.7.1にdockerfile ローカル環境変更した結果今回のエラーは解決しました。

 参考記事

ruby version変更https://qiita.com/_kanacan_/items/c1499f6c13b1c41da982

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

Kubernetes minikube エラー

エラー

ssh接続後、minikube stopコマンドを入力するとエラーが発生する
image.png

解決

単にプロファイルが壊れているだけなので、再度起動すればよい
プロファイル削除
minikube delete
image.png

起動
minikube start or minikube start --vm-driver=none(virtualbox未インスト)
再度ステータス確認
minikube status

image.png

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

Dockerについて(アウトプット)

Dockerについての理解を深めるためアウトプットとしてこの記事を残します。

Dockerとは

Docker社が提供する「コンテナ型仮想化技術」 を実現するプロダクト

仮想化とは何か

PCやサーバといったマシンにインストールされているOS(ホストOS)の上に、別のマシンを仮想的に立ち上げる事。
つまり「パソコンの中に仮想パソコンを起動する」というのが仮想化。
インターネットの普及を支えているのは、Webサーバーをはじめとした各種サーバーという機械のおかげ。ただ、世の中のWebサイトの爆発的な増加にともなって機会も同じように増加していったらサーバーの置き場所、費用、管理が膨大になる。そこで1台のサーバー上に、複数のサーバーとして利用できる仕組みを構築する方法として生まれたのが仮想化という技術。

数ある仮想化技術でDockerを使うメリット

まず他の仮装技術と比べると、パソコンに負荷を与えない軽量設計になっている。また環境構築における差異の最小化を図ることができる。例えば、設定を変えたりした場合、全員のDockerfileを書き換えてビルドするのではなく、設定が変わったコンテナをイメージ化して配布した方が手作業も減るし、環境差異を減らすことにもつながる。

よく使うDockerコマンド

イメージ一覧

docker images

コンテナの稼働状況確認

docker ps

docker-compose up buildstart の違い

build コマンドではimageを構築します。コンテナは作成しない

upコマンドでは、キャッシュがある場合はそれを使って一発でイメージの構築から、コンテナの構築・起動までできる。imageがなくてもbuildから実行できる。

startコマンドでは既存のコンテナを起動することができる。

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

MacでCentOSをさわりたい(docker)

Macも使ってあげたい

Mac book買ってもらったのにAmazonPrimeしか見てないんです、、、
これではあまりにもかわいそうなのでそろそろ別の使い方をしよう!!と思い、この記事を書くことにしました。
でも、あんまり容量減らしたくないので今回はdockerを使ってCentOSの環境を作ります、!!

そもそもdockerってなによ

dockerはですね、簡単に言うとコンテナ技術ですね。
これを使うと一瞬で自分のパソコンの中にもう一台別のパソコン的なものを作ることができちゃうんです!
仮想化とは異なり、ホストOSと同じカーネルを使うことで容量の大量消費を防いでくれます。
dockerについては今度ちゃんとまとめるつもりなので気になる方はぺぺっと検索してみてください!(私のちゃんとまとめるつもりシリーズ大量にあるね☺️)

さぁCentOS7の環境を構築しよう!

[前提]
ホストOS : macOS Catalina 10.15.5
構築したいOS : CentOS7

[ディレクトリ構成]
-- docker/
      ∟ example/
         ∟ Dockerfile

exampleの中で作業していきます。
Dockerfileは後で自分で書きます。

それではCentOS7を構築してみましょう!
DockerHub
ここからMacで使用できるdockerをインストールしてください。
インストールが終わったらFinderからダウンロードしたDocker.dmgをポチッと。
そうするとdrag and dropの指示がでるので指示通りに。
(ダウンロードアイコンのところからこの作業しようとしたらなんかできなかった、なんで、、、)
はい!これでLaunchpadにdockerが追加されました!
docker(クジラのアイコン?)をクリックすると右側にターミナルが開かれるので、左側の指示にしたがって進んでください。
これでターミナルにdockerが入りました!
試しに新しくターミナルを開いて

docker

とコマンドを入力してみてください。動きますね?

次に

docker run centos:7

と入力してください。これでローカル環境にcentos7のimageが入りました。

docker images

これで確認できます。
どこかにcentosって文字が見つかるはず!笑

はい!それではDockerfileも書いてみましょう。

FROM centos:7

LABEL maintainer="miyabi <miyabi@example.com>"

RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \
    && yum -y install python3

CMD ["/usr/bin/bash"]

LABELのメールアドレスとか名前はテキトーでも動きます!

これは自分的なCentOSの環境のレシピのようなものなのでもっと使いやすくしたいところ。

そのままexampleディレクトリにステイしたままで次のコマンドを実行してください。

docker build -t miyabi .

miyabiのところはimage名なので好きなのをつけて大丈夫です。
ヤッタァ!!これで自分のimageが完成です。

docker images

ほら!miyabi(自分でつけたimage名)ができてるでしょ!!
ついにコンテナ作るよ!!!

docker run -it miyabi

miyabiのところは先ほど自分がつけたimage名に変えてくださいね。
わぁ!これで自分で構築した環境に入り込めてますね!!
試しに

python3

と入力してみてください。
動くでしょ??
Dockerfileにpython3をインストールしてくれるように記入したので入っているんです!
今回はCMDのところにrunしたらコンテナ内に入り込むように書いたのでrunした時点でそのまま動かすことが可能ですが、もっと色々アレンジできますよん。

注意

ctrl + Dで環境から抜け出すことができますが、セーブしないと次回コンテナ内に入り込んだ時にまっさらな状態になっているので気をつけてくださいね!

おわりに

dockerって便利!慣れてくるとDockerfileささっとかけばたくさんの簡易的な環境をすぐに作り上げることができますね〜
勉強不足なのでまだこれといった恩恵はまだ受けていませんが、絶対すごいじゃん!!って感じですね〜!!!
すぐたてられるし、すぐ壊せるし!!
今回のだとDockerfileの内容がペラペラすぎてpython3しか使えないけど、macでcentOS使ってると思うとなんか嬉しい☺️
間違っていたらどんどん突っ込んでください!!
最後まで読んでいただきありがとうございます。

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

docker-compose.yml を環境ごとに分割する

前提

Compose ファイルフォーマットバーション 3.x

結論

-f オプションを使う

$ docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

または環境変数を使う: COMPOSE_FILE, COMPOSE_PATH_SEPARATOR

$ export COMPOSE_PATH_SEPARATOR=:
$ export COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
$ docker-compose up -d

ファイル間、プロジェクト間での Compose 設定の共有 — Docker-docs-ja 17.06 ドキュメント

注意

  • extends キーワードは 2.1 までのサポートで、(2020年7月現在)ではご利用いただけません?‍♀️
  • COMPOSE_FILEの区切り文字はOSによって異なるのでCOMPOSE_PATH_SEPARATORを明示的に指定しておくと無難
  • -fオプションではなく環境変数を使う場合は他プロジェクトに影響するかもしれないのでそこは注意

Makefile と合わせて使う

簡単な例としてベースとなるファイルと、環境用のファイルをそれぞれ用意した。

docker-compose.yml
version: "3"
volumes:
  app-node_modules:
  app-dist:
services:
  app:
    build: ./app
    container_name: app
    volumes:
      - ./app/src:/usr/app
      - app-node_modules:/usr/app/node_modules
      - app-dist:/usr/app/dist
docker-compose.prod.yml
version: "3"
services:
  app:
    ports:
      - 80:80
    environment:
      PRODUCTION: 'true'
docker-compose.local.yml
version: "3"
services:
  app:
    ports:
      - 8080:80
    environment:
      DEBUG: 'true'

これらを環境によって切り替えるための Makefile を作る

ここでは環境変数を使って docker-compose の上書きを想定した

Makefile
# 本番では make [cmd] e=prod とする

# local or prod
ENV=local

pre:
ifdef e
ENV=${e}
endif

set-env := export ENV=$(ENV) ;\
           export COMPOSE_PATH_SEPARATOR=: ;\
           export COMPOSE_FILE=docker-compose.yml:docker-compose.$(ENV).yml

up: pre
    $(set-env)\
    docker-compose up -d

down: pre
    $(set-env)\
    docker-compose down

rebuild: pre
    $(set-env)\
    docker-compose build --no-cache

上記の設定で環境ごとにコマンドで切り替えられるようになった?

# ローカル開発
$ make rebuild
$ make up
$ make down

# 本番環境
$ make rebuild e=prod
$ make up e=prod
$ make down e=prod

これは簡単な例だが、実際にはより多くの設定になるため、多くの冗長な設定から解放されて幸せになれる❤️

Makefile がないとメンテナンス性も開発体験も落ちるので、嫌がらずに開発序盤から用意しよう?

結合時の挙動について

1つの値を持つオプションか、複数の値を持つオプションかで挙動が変わってくる

以下公式より引用: 設定の追加と上書き — Docker-docs-ja 17.06 ドキュメント

1 つの値しか持たないオプション、たとえば image、command、mem_limit のようなものは、古い値が新しい値に置き換えられます。

# docker-compose.yml
command: python app.py

# docker-compose.prod.yml
command: python otherapp.py

# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
command: python otherapp.py

# 後に書いた方が適応される

複数の値を持つオプション、つまり ports、 expose、 external_links、 dns、 dns_search、 tmpfs では、両者の設定をつなぎ合わせます。

# docker-compose.yml
expose:
  - "3000"

# docker-compose.prod.yml
expose:
  - "4000"
  - "5000"

# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
expose:
  - "3000"
  - "4000"
  - "5000"

# 両者適応される

environment、 labels、 volumes、 devices の場合、Compose は設定内容を "マージ" して、ローカル定義の値が優先するようにします。

# docker-compose.yml
environment:
  - FOO=original
  - BAR=original

# docker-compose.prod.yml
environment:
  - BAR=local
  - BAZ=local

# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
environment:
  - FOO=original
  - BAR=local
  - BAZ=local

# 競合してたら後に書いた方が優先され、競合してないものは適応される

? Compose ファイルのご利用は計画的に?

(ブログに書いた内容そのまま転載した)

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

PodmanのVolumeを使ってMySQLを永続化する

はじめに

前回の記事 に引き続いて、Docker の代わりに Podman を使うためにどのような差分があるのかを見ていく。

Podman Volume

MySQLなどをサーバー上で利用する場合、ボリュームを永続化しておく必要がよい (そうしないと、コンテナの削除などでデータが消えてしまうなど問題が出る)。 この時、Docker では docker volume で永続化したボリュームを作成してこれを利用する。
Podman の場合も同様に podman volume コマンドがあるので、これを利用してボリュームを作成できる。 この時は Podman で用意されたと考えられるデフォルトディレクトリがマウントされる。

Mountpointを参照
# rootless の場合
$ podman volume create sample
sample
$ podman volume inspect sample
[
     {
          "Name": "sample",
          "Driver": "local",
          "Mountpoint": "/home/centos/.local/share/containers/storage/volumes/sample/_data",
          "CreatedAt": "2020-07-13T17:41:12.27273779+09:00",
          "Labels": {

          },
          "Scope": "local",
          "Options": {

          },
          "UID": 0,
          "GID": 0,
          "Anonymous": false
     }
]

# rootful な場合
$ sudo podman volume create sudo-sample
sudo-sample
$ sudo podman volume inspect sudo-sample
[
     {
          "Name": "sudo-sample",
          "Driver": "local",
          "Mountpoint": "/var/lib/containers/storage/volumes/sudo-sample/_data",
          "CreatedAt": "2020-07-13T17:41:58.770498487+09:00",
          "Labels": {

          },
          "Scope": "local",
          "Options": {

          },
          "UID": 0,
          "GID": 0,
          "Anonymous": false
     }
]

特定パスをマウントしたボリュームの作成

個人的には特定のパス (例えば、 /opt/mount/mysql )をマウントポイントにしたい、といったケースがある。
この場合、Docker では 例えばこちらの記事にあるように docker-compose.yml に以下のような記述を書いて volume をマウントしていた。

特定ディレクトリをマウントしたVolumeを作成するためのdocker-compose.ymlの一部
volumes:
  datastore:
    driver_opts:
      type: none
      device: /opt/mount/mysql
      o: bind

これは同様の方法の内容を podman コマンドで実現して、ボリュームを作成できる。

$ podman volume create -o type=none -o device=/opt/mount/mysql -o o=bind datastore

ただ、この方法で作成したボリュームは rootful でなければマウントできないようだ。
そのため、もしこの方法を使うというのであれば rootful なコンテナに対して利用する。

$ podman run --rm -v datastore:/tmp2 nginx touch /tmp2/hoge
Error: error mounting volume datastore for container 549045ccdd392987427598764ffc0b533fdb5d6a6b31bd4172aa28282bc15e2d: cannot mount volumes without root privileges: operation requires root privileges

……のだが、ルートで実行するとどうなるか。 以下の通り Permission denied ではじかれてしまう。 ディレクトリパーミッションが 777 であるにも関わらず、である。

# パーミッションは問題ない
$ ls -l /opt/mount/
total 0
drwxrwxrwx. 2 root root 6 Jul 13 18:00 mysql

$ sudo podman run --rm -v datastore:/tmp2 nginx touch /tmp2/hoge
touch: cannot touch '/tmp2/hoge': Permission denied

マウントボリュームが Permission denied

今回の検証環境が RedHat 系ということで、心当たりがある人がいるかもしれないが、結論から言えば原因は SELinux である。 今回の検証環境では sestatus = enabled の状態で検証している。 公式のトラブルシュートで2番目に述べられている通り、非常に多くの人が引っかかる問題のようだ。 紹介されている解消法は2つ。

  • -v の第三引数として :z あるいは :Z オプションをつける
    • Docker にも存在するオプションで、SELinux用のラベルの付け替えを行う
  • --security-opt label=disable を指定することでコンテナに対するセキュリティラベリングを無効化する
    • こちらも同様にDockerにも存在するオプション

今回は後者のオプションをつけて、SELinuxのセキュリティラベリングを無効化して動作させる。 これでマウントができていることが確認できた。

指定したマウントディレクトリに結果が出ていることを確認
$ sudo podman run --security-opt label=disable --rm -v datastore:/tmp2 nginx touch /tmp2/hoge
$ ls -l /opt/mount/mysql/
total 0
-rw-r--r--. 1 root root 0 Jul 13 18:28 hoge

MySQL の永続化

ということで本題。 一度 sudo podman rm -af && sudo podman volume rm -af を実施して、一通り削除してから開始します。

# ボリューム datastore を作成
$ sudo podman volume create -o type=none -o device=/opt/mount/mysql -o o=bind datastore
datastore
# マウントしてMySQLを起動
$ sudo podman run --security-opt label=disable -d -v datastore:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=sample mysql 
a40dc20b6246411acc57bb0c7b19825579f95acfe25296685009f7c89e1a9c18
# MySQL DBに接続してデータベースを作成 (内容を変化させる)
$ sudo podman exec -it a40 mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.20 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database hogehoge;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| hogehoge           |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> 
mysql> exit;
Bye
# マウントしたホストのディレクトリにデータが書き込まれていることを確認
$ ls -l /opt/mount/mysql/
total 186776
-rw-r-----. 1 systemd-coredump input       56 Jul 13 18:34  auto.cnf
-rw-r-----. 1 systemd-coredump input  3104223 Jul 13 18:34  binlog.000001
-rw-r-----. 1 systemd-coredump input      353 Jul 13 18:34  binlog.000002
-rw-r-----. 1 systemd-coredump input       32 Jul 13 18:34  binlog.index
-rw-------. 1 systemd-coredump input     1680 Jul 13 18:34  ca-key.pem
-rw-r--r--. 1 systemd-coredump input     1112 Jul 13 18:34  ca.pem
-rw-r--r--. 1 systemd-coredump input     1112 Jul 13 18:34  client-cert.pem
-rw-------. 1 systemd-coredump input     1676 Jul 13 18:34  client-key.pem
drwxr-x---. 2 systemd-coredump input        6 Jul 13 18:34  hogehoge
-rw-r-----. 1 systemd-coredump input   196608 Jul 13 18:34 '#ib_16384_0.dblwr'
-rw-r-----. 1 systemd-coredump input  8585216 Jul 13 18:34 '#ib_16384_1.dblwr'
-rw-r-----. 1 systemd-coredump input     5642 Jul 13 18:34  ib_buffer_pool
-rw-r-----. 1 systemd-coredump input 12582912 Jul 13 18:34  ibdata1
-rw-r-----. 1 systemd-coredump input 50331648 Jul 13 18:34  ib_logfile0
-rw-r-----. 1 systemd-coredump input 50331648 Jul 13 18:34  ib_logfile1
-rw-r-----. 1 systemd-coredump input 12582912 Jul 13 18:34  ibtmp1
drwxr-x---. 2 systemd-coredump input      187 Jul 13 18:34 '#innodb_temp'
drwxr-x---. 2 systemd-coredump input      143 Jul 13 18:34  mysql
-rw-r-----. 1 systemd-coredump input 30408704 Jul 13 18:34  mysql.ibd
drwxr-x---. 2 systemd-coredump input     8192 Jul 13 18:34  performance_schema
-rw-------. 1 systemd-coredump input     1680 Jul 13 18:34  private_key.pem
-rw-r--r--. 1 systemd-coredump input      452 Jul 13 18:34  public_key.pem
-rw-r--r--. 1 systemd-coredump input     1112 Jul 13 18:34  server-cert.pem
-rw-------. 1 systemd-coredump input     1680 Jul 13 18:34  server-key.pem
drwxr-x---. 2 systemd-coredump input       28 Jul 13 18:34  sys
-rw-r-----. 1 systemd-coredump input 10485760 Jul 13 18:34  undo_001
-rw-r-----. 1 systemd-coredump input 12582912 Jul 13 18:34  undo_002

上で作ったコンテナ・ボリュームをわざと削除して、データが削除されていないかを確認します。

# Podman上のコンテナとボリュームを全削除
$ sudo podman rm -af 
a40dc20b6246411acc57bb0c7b19825579f95acfe25296685009f7c89e1a9c18
$ sudo podman volume rm -af 
datastore

# 再度ボリュームをマウントし、MySQLコンテナを生成
$ sudo podman volume create -o type=none -o device=/opt/mount/mysql -o o=bind datastore
datastore
# -- NOTE: ここで既に初期化済の MySQL データをバインドしているのでパスワードは不要
$ sudo podman run --security-opt label=disable -d -v datastore:/var/lib/mysql mysql 
a5a2dc5121ce3bacc805d2f3a227af4f43edb48c083a4ca8c381029f38cc1c5e

# 消去したPodmanのボリューム内に変更されたデータがローカル側にバインドされていることを確認
# (Database: hogehoge が存在する)
$ sudo podman exec -it a5a mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.20 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| hogehoge           |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)

ということで永続化ができていることが確認できます。

まとめ

この部分はほとんど Docker と同じでした。

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

NVIDIA Container ToolkitをインストールしてDockerコンテナでGPUを使用する

Dockerコンテナからホスト上のGPUを使用するためにNVIDIA Container Toolkitの導入手順を記載します。今回GPUのサーバーとしてはIBM Cloudの仮想サーバーを使用します。OSはCentOS 7.7、GPUはP100 1個のサーバーになります。
image.png

1. NVIDIAドライバーのインストール

以下リンク先から適切なドライバーをダウンロードしてインストールします。
https://www.nvidia.co.jp/Download/index.aspx?lang=jp

image.png

$ sudo yum install gcc -y
$ sudo yum install kernel-devel -y
$ wget http://jp.download.nvidia.com/tesla/450.51.05/NVIDIA-Linux-x86_64-450.51.05.run
$ sudo sh cuda_10.2.89_440.33.01_linux.run  --kernel-source-path=/usr/src/kernels/3.10.0-1127.13.1.el7.x86_64

インストールが成功すると nvidia-smi コマンドで GPU の情報が表示されます。

$ nvidia-smi 
Mon Jul 13 03:14:11 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.51.05    Driver Version: 450.51.05    CUDA Version: 11.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla P100-PCIE...  Off  | 00000000:00:07.0 Off |                    0 |
| N/A   32C    P0    29W / 250W |      0MiB / 16280MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

2. Dockerのインストール

公式ガイドに記載のとおり、Dockerをインストールします。

古いバージョンがインストールされていたらアンインストール

$ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

リポジトリのセットアップ

$ sudo yum install -y yum-utils

$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

Dockerのインストール

$ sudo yum install docker-ce docker-ce-cli containerd.io

一般ユーザーに権限を付与しておく

sudo usermod -aG docker $USER

3. NVIDIA Container Toolkitのインストール

Quickstartに記載のとおりインストール

$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo

$ sudo yum install -y nvidia-container-toolkit
$ sudo systemctl restart docker

以下のように docker コマンドで nvidia-smi の結果が返ってきたら成功。

$ docker run --gpus all nvidia/cuda:10.0-base nvidia-smi
Unable to find image 'nvidia/cuda:10.0-base' locally
10.0-base: Pulling from nvidia/cuda
7ddbc47eeb70: Pull complete 
c1bbdc448b72: Pull complete 
8c3b70e39044: Pull complete 
45d437916d57: Pull complete 
d8f1569ddae6: Pull complete 
de5a2c57c41d: Pull complete 
ea6f04a00543: Pull complete 
Digest: sha256:e6e1001f286d084f8a3aea991afbcfe92cd389ad1f4883491d43631f152f175e
Status: Downloaded newer image for nvidia/cuda:10.0-base
Mon Jul 13 08:31:01 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.51.05    Driver Version: 450.51.05    CUDA Version: 11.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla P100-PCIE...  Off  | 00000000:00:07.0 Off |                    0 |
| N/A   34C    P0    29W / 250W |      0MiB / 16280MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

テスト

こちらからTensorFlowのコンテナを起動します。--gpusオプションを使用するとコンテナからGPUが使用可能となります。

$ docker run -it --gpus all -p 8888:8888 tensorflow/tensorflow:latest-gpu-jupyter

以下のようにJupter Notebookの接続先が表示されるので、ホスト名の部分をサーバーのIPアドレスに置き換えて、ブラウザでアクセスします。

http://10313cb03051:8888/?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

チュートリアルのNotebookが表示されるので実行してみます。
image.png

深層学習によるモデル構築も高速に動いてくれました。
image.png

参考

以下のように起動のオプションでDocker設定が可能です。

$ mkdir ~/workspace
$ docker run -it --gpus all -p 8888:8888 -u $(id -u):$(id -g) -v ~/workspace:/tf tensorflow/tensorflow:latest-gpu-jupyter

-v : ホストのディレクトリをコンテナ上にマウント
-u : コンテナの実行ユーザー、グループを変更

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

dockerでnpx @angular/cli newしてエラーが出る時の対処法

問題

npxを使うと@angular/cliをグローバルインストールせずにプロジェクトを作成できます。

npx @angular/cli new foo

しかしdockerで同じコマンドを実行するとエラーになります。

internal/modules/cjs/loader.js:1033
  throw err;
  ^

Error: Cannot find module '/root/.npm/_npx/800/lib/node_modules/@angular/cli/bin/postinstall/script.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:1030:15)
    at Function.Module._load (internal/modules/cjs/loader.js:899:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @angular/cli@10.0.2 postinstall: `node ./bin/postinstall/script.js`
npm ERR! Exit status 1

回避策

どうもrootユーザーなのが問題なようです。
--unsafe-permを指定したいところですがnpxには--unsafe-permオプションが無さそうなので環境変数で渡します。

npm_config_unsafe_perm=true npx @angular/cli new foo

これで無事にプロジェクトを作成できました。

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

OKI AI エッジコンピューター「AE2100」でOpenVINOのサンプルプログラムを動かしてみよう(1)

要約

  • この資料は、OKI AI エッジコンピューター「AE2100」向けの設定解説です。
  • GUIアプリケーション動作のためのVcXsrv(X-Windowクライアント)の導入方法を解説します。

はじめに

Intel OpenVINOには様々なサンプルプログラムが付属しています。
ですが、多くのサンプルプログラムはウインドウの表示が必要になっており、
ディスプレイ出力がないAE2100では動作ができません。

そこで、数回に渡ってAE2100でOpenVINOのサンプルプログラムを動かすための方法をご紹介していきます。
初回はAE2100を接続したウインドウズPCにVcxSrvの導入をおこないます。

環境

以下のような構成で、ウインドウズPCからAE2100へTeraTermでコンソール接続できることを前提とします。
(適宜IPアドレスなどはご使用環境に合わせ読み替えてください。)
なおAE2100のコンテナバージョンは「centos7_openvino_2019R31_1.tgz」とします。

image.png

VcXsrv のインストール

VcXsrv とはフリーの X Server クライアントで、ウインドウズPCにインストールをおこないます。
WebブラウザよりSourceForge.netにアクセスし “VcXsrv” のインストーラーをダウンロードします。
https://sourceforge.net/projects/vcxsrv/
image.png

ダウンロードしたインストーラを実行すると以下の画像のような画面が出ます。
設定はそのままにし[Next]を押します。
image.png

インストール先のフォルダを確認・選択して「Install」をクリックします。
image.png

インストールが完了したら「Close」をクリックします。
image.png

VcXsrvの起動

ウインドウズPCのスタートメニューからVcXsrv→Xlaunchを起動します。
image.png

設定はそのままにし「次へ」をクリックします。(”Multiple Window”を選択)
image.png

設定はそのままにし「次へ」をクリックします。(Start no clientを選択)
image.png

「Disable access control」のチェックを入れて「次へ」をクリックします。
image.png

Save configrationで設定を保存し、[完了]を押します。
image.png

以下の画像のような画面が出る場合、[アクセスを許可する]を押します。
image.png

そして、タスクバー右下にXlaunchのアイコンが表示されれば成功です。
image.png

AE2100のコンテナ設定

TeraTramでAE2100のホストOSにログインして、以下のコマンドでコンテナに入ります。
(コンテナが起動していない場合、「AE2100 シリーズ SDK 取扱説明書 ーDeepLearning 編ー」P.20を参考に起動させてください。)

root@ae2100:~# docker exec -it centos-openvino /bin/bash 

 
今回は電卓アプリケーションを動かします。以下のコマンドでインストールをおこないます。

# yum install xcalc

 
ウインドウ表示先のIPアドレスを設定します。PCのIPアドレスを指定します。

# export DISPLAY=192.168.100.101:0.0

GUIアプリケーションの実行

AE2100のコンテナ内で以下のコマンドを入力します。

# xcalc &

 
ウインドウズPCのデスクトップに電卓アプリが表示されます。
image.png

試しにボタンを押して計算をさせてみましょう。
例えば「8」「*」「8」と入力すると「64」と表示されます。
アプリケーションウインドウはPC上に表示されていますが、計算自体はAE2100でおこなわれています。
image.png

もし電卓が表示されない場合は、ウインドウズの「ファイアウォールとネットワーク保護」
を確認してください。
ウインドウズの「ファイアウォールとネットワーク保護」画面を開き、
「ファイアウォールによるアプリケーションの許可」を選択します。
image.png

設定変更ボタンを押し、下画面のようにVcXsrv windows xserverにチェックを入れOKボタンを押します。
image.png

まとめ

今回はAE2100に接続したPCのウインドウ表示設定をおこないました。
次回からはOpenVINOのサンプルプログラムを動かしていきます。
 

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

Alibaba Cloud Container Registry Serviceを使用したコンテナ化されたイメージの構築と展開

このチュートリアルでは、Alibaba Cloud Container Registryサービスを使用してコンテナ化されたイメージを構築し、デプロイします。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Alibaba Cloud Container Registryとは?

Alibaba Cloud Container Registry(ACR)は、コンテナイメージを構築して保存し、Dockerイメージを配布できるようにするスケーラブルなサーバーアプリケーションです。ACRを使用すると、保存されたイメージを完全に制御することができます。ACRには、GitHub、Bitbucket、自作のGitLabとの統合など、多くの機能があります。また、コンパイル後に新しいイメージを自動的にビルドし、ソースコードからアプリケーションまでテストすることもできます。

ステップ1:Alibaba Cloud Container Registryを有効化する

アリババクラウドのアカウントを設定する必要があります。アカウントをお持ちでない場合は、アカウントにサインアップして、40以上の製品を無料でお試しいただけます。詳しくはこちらのチュートリアルをお読みください。
最初に行う必要があるのは、Alibaba Cloud Container Registryを有効化することです。製品ページに移動し、Get it Freeをクリックします。
Container Registry Consoleに移動し、サービスを設定してデプロイすることができます。

ステップ2:アリババクラウドコンテナレジストリの設定

ネームスペースの作成

ネームスペースはリポジトリの集合体、リポジトリはイメージの集合体です。私は、アプリケーションごとに1つのネームスペースを作成し、サービスイメージごとに1つのリポジトリを作成することをお勧めします。

image.png

ネームスペースを作成したら、設定でパブリックリードかプライベートかを設定します。

ローカルリポジトリの作成とアップロード

リポジトリ(repo)とは、イメージを集めたものです。1つのサービスのイメージの全バージョンを1つのリポジトリに集めることをお勧めします。Create Repoをクリックし、ページ内の情報を記入します。Local Repositoryを選択します。しばらくすると、独自のリポジトリURLを持つ新しいリポジトリが作成されます。画像一覧ページに表示されています。

これで、ローカルで作成した画像をこのリポジトリにアップロードすることができます。

image.png

ステップ3:Dockerクライアントでコンテナレジストリに接続する

Dockerクライアントから任意のコンテナレジストリに接続するためには、まずACRコンソールでDockerのログインパスワードを設定する必要があります。このパスワードを使ってDockerクライアントからレジストリにログインします。

image.png

次に、イメージ一覧ページで、接続したいリポジトリの前にあるAdminをクリックします。ここには、Dockerクライアントがリポジトリにアクセスできるようにするために必要な情報やコマンドが記載されています。リポジトリのイメージ名、イメージタイプ、インターネットアドレス、イントラネットアドレスが表示されます。インターネットアドレスは、世界中のどこからでもリポジトリにアクセスできるようにするためのものです。Alibaba Cloudコンテナクラスタでリポジトリを使用する場合は、インターネットアドレスを使用した方がはるかに高速になるので、インターネットアドレスを使用した方が良いでしょう。

ログインコマンド、プッシュコマンド、プルコマンドをコピーします。後で必要になります。

image.png

ローカルマシンでDockerクライアントを起動します。Dockerクライアントのインストール方法はdocker.ioを参照してください。MACの場合はdocker.appを起動してDockerクライアントを起動します。

Dockerクライアントのユーザーとしてログイン

docker login --username=random_name@163.com registry-intl.ap-southeast-1.aliyuncs.com

注: random_name は実際のユーザー名に置き換えてください。

パスワードを入力してエンターキーを押すと、ログイン成功のメッセージが表示されます。この時点で認証され、Alibaba Cloud Container Registryに接続されています。

image.png

ステップ4: ローカルにイメージを構築し、ACRにプッシュ

Dockerfileを書いてイメージを構築してみましょう。以下はサンプルのDockerfileです。

######################
# This is the first image for the static site.
#####################
FROM nginx
#A name can be given to a new build stage by adding AS name to the FROM instruction.
#ARG VERSION=0.0.0
LABEL NAME = static-Nginx-image 
START_TIME = 2018.03.10 
FOR="Alibaba Community" 
AUTHOR = "Fouad"
LABEL DESCRIPTION = "This image is built for static site on DOCKER"
LABEL VERSION = 0.0.0
#RUN mkdir -p /var/www/
ADD /public /usr/share/nginx/html/
EXPOSE 80
RUN service nginx restart</code></pre>

Run the Docker build command to build the image. In order to later push the image to the repository, you need to tag the new image with the registry: 

<pre><code>docker build -t registry-intl-internal.ap-southeast-1.aliyuncs.com/fouad-space/ati-image .

image.png

ビルドが完了すると、すでにリポジトリ名がタグ付けされています。コマンドを使うと新しい画像が入っているのがわかります。

Docker image ls

image.png

コマンドで画像をACRリポジトリにプッシュします。

docker push registry-intl.ap-southeast-1.aliyuncs.com/fouad-space/ati-image:latest

image.png

イメージが正常にプッシュされたことを確認するには、Container Registryコンソールで確認します。リポジトリ名の前にある Admin をクリックし、Image version をクリックします。

image.png

画像をプルしてコンテナを作成します。docker pullコマンドを実行します。

docker pull registry-intl.ap-southeast-1.aliyuncs.com/fouad-space/ati-image:latest

image.png

すでにローカルコンピュータに画像を引っ張ってきているので、「画像は最新のものです」というメッセージが表示されています。

この画像を使って新しいコンテナを作成します。

docker run -ti -p 80:80 registry-intl.ap-southeast-1.aliyuncs.com/fouad-space/ati-image bash

image.png

ステップ 5: GitHub を使ったイメージレポの構築

Alibaba Cloud Container Registryを使用すると、クラウド上でイメージをビルドするだけでなく、レジストリに直接プッシュすることもできます。これに加えて、Container Repositoryは、コードが変更されたときにビルドを自動的にトリガーする機能をサポートしています。

ビルド設定で「コード変更時にイメージを自動的に作成する(Automatically create an image when the code changes)」を選択すると、コードを送信した後にイメージを自動的にビルドすることができ、手動でビルドをトリガーする必要が無くなります。これにより、手作業を省くことができ、イメージを常に最新の状態に保つことができます。

GitHubのレポを作成し、Dockerファイルをレポにアップロードします。

image.png

その後、コンテナレジストリコンソールに戻り、レポを作成します。GitHubのレポパスを選択し、リポジトリ作成の手順を完了します。

image.png

リポジトリが作成されたら、Image Listに移動し、レポ名のAdminをクリックし、Buildをクリックし、最後にBuild Nowをクリックします。

メニューにビルドの進捗状況が表示され、ビルドプロセスの完全なログが表示されます。

image.png

ビルドログも全て見ることができます。きちんとしていると思いませんか?

image.png

ビルドが完了したら、イメージをデプロイする準備ができています。これをローカルのDockerエンジンに引っ張ったり、Alibaba Cloud Container Serviceにこのイメージをデプロイしたりすることができます。

ステップ6: Webhookトリガーの作成

Webhookはトリガーの一種です。これを設定すると、イメージがビルドされたときに通知をプッシュしてくれるので、継続的なインテグレーションパイプラインを設定することができます。
これはどのように動作するのでしょうか? Webhook に Container Service のトリガーを設定したとします。イメージがビルドされたり、リビルドされたりすると、コンテナサービス内のアプリケーションが自動的にトリガーされて、最新のイメージを引っ張ってきて再デプロイされます。

Webhookを作成するには、まずコンテナサービスにアクセスして、アプリケーションのWeb URLを取得する必要があります。

image.png

ここで、この URL を使用してフックを設定します。コンテナレジストリのイメージが更新されるたびに、このアプリケーションは新しいイメージで再デプロイされます。間違った設定をするとアプリケーション全体がダウンしてしまう可能性がありますので、十分に注意してください。しかし、コンテナサービスではロールバックが可能なので、大きな心配はありません。

image.png

概要

今回の記事では、以下のことを学んだはずです。

  • Alibaba Cloud Container Registryサービスとは何か、どのように実装することができるか。
  • Dockerイメージをホストするためのネームスペースとリポジトリを作成する方法。
  • ローカルにDockerイメージを構築してACRにプッシュする方法。
  • DockerイメージをACRから引っ張ってきて、それを使って新しいスコテナーをインスタンス化する方法。
  • GitHubソースコードを使ってコンテナレジストリにイメージをビルドする方法。
  • 最新のイメージのプルリクエストを自動的にトリガーしてサービスを再デプロイする方法。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

「Docker/Kubernetes」Kubernetesの拡張リソース・デバイスプラグインモジュール

この記事では、バージョン1.11で導入される予定のKubernetesの拡張リソースとデバイスプラグインモジュールについて探っていきます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

KubernetesのGPUのスケジューリングや運用の仕組みについて最近報じられた記事によると、バージョン1.11では従来のalpha.kubernetes.io/nvidia-gpuのメインコードを非推奨とし、GPU関連のスケジューリングやデプロイメントコードを完全にメインコードから削除し、その代わりに、Kubernetesの組み込みモジュールである拡張リソースデバイスプラグインの2つのモジュールと、デバイスプロバイダが開発したデバイスプラグインを組み合わせて、デバイスクラスタからワーキングノードへのスケジューリングを実装し、デバイスをコンテナでバインドするというものです。

Kubernetesの2つのモジュールについて簡単に紹介します。

  • 拡張リソース
    これはカスタムリソースの拡張方法です。開発者は、リソース名とリソースの総数をAPIサーバーに報告する必要があります。スケジューラは、リソースポッド内の作成・削除に基づいて利用可能なリソース数を増減させ、スケジューリング時にリソース要件を満たすノードを決定します。拡張リソースの増分、減分は整数でなければなりません。例えば、1GPUを割り当てることはできますが、0.5GPUを割り当てることはできません。この関数は不透明な整数リソースを置き換え、いくつかの名前を変更するだけなので、バージョン1.8では安定しています。キーワード整数を削除すれば、将来的には0.5 GPUを割り当てることができるようになるのでしょうか?

  • デバイスプラグイン
    一般的なデバイスプラグイン機構と標準デバイスAPIインタフェースを提供します。機器ベンダーは、Kubeletのメインコードを変更することなく、APIを実装することで、GPU、FPGA、高性能NIC、InfiniBandなどのデバイスを拡張することができます。この機能は、Kubeernetes 1.8と1.9のAlpha版にあり、Kubeernetes 1.10のBeta版にも搭載される予定です。この機能はまだ新しい機能なので、--feature-gate=DevicePlugins=trueに設定して有効にする必要があります。

デバイスプラグインの設計

API設計

実は、Device Plugin は ListAndWatchAllocate メソッドを実装したシンプルな gRPC サーバで、/var/lib/kubelet/deviceplugins/以下の Unix ソケット、例えば /var/lib/kubelet/deviceplugins/nvidia.sock をリッスンしています。

service DevicePlugin {
    // returns a stream of []Device
    rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}
    rpc Allocate(AllocateRequest) returns (AllocateResponse) {}
}

これらの中では
- ListAndWatch:Kubelet はこの API を呼び出してデバイスを検出し、デバイスの状態を更新します (例えば、デバイスが不健康になるなど)。
- Allocate:Kubelet がデバイスを使用するコンテナを作成する際に、Kubelet はこの API を呼び出してデバイスに対する操作を行い、コンテナの初期化に必要なデバイス、ボリューム、環境変数の設定を取得します。

プラグインのライフサイクル管理

プラグインが起動すると、Kubelet /var/lib/kubelet/device-plugins/kubelet.sock に GRPS 形式で登録し、プラグインのリスニング用 Unix ソケット、API バージョン、デバイス名 (例: nvidia.com/gpu) を提供します。Kubelet はデバイスをノードの状態で公開し、Extended Resource リクエストで API サーバに送信します。スケジューラは、その情報に基づいてデバイスをスケジュールします。

プラグインが起動すると、Kubeletはプラグインへの永続的なlistAndWatch接続を確立します。不健全なデバイスを検出すると、プラグインは自動的にKubeletに通知します。デバイスがアイドル状態であれば、Kubelet はそのデバイスを割り当て可能なリストから削除します。

プラグインは、Kubelet ソケットを使って Kubelet の状態を監視します。Kubelet が再起動すると、プラグインも再起動し、再び Kubelet に登録します。

image.png

デプロイメント方法

一般的には、デーモンセットの展開と非コンテナ展開をサポートしています。ただし、公式にはデーモンセットの展開を推奨しています。

実装例

Nvidia公式GPUプラグイン
NVIDIAは、ユーザーフレンドリーなGPUデバイスプラグインNVIDIA/k8s-device-pluginを提供しており、これはDevice Pluginsインターフェースをベースにしています。従来の alpha.kubernetes.io/nvidia-gpu のように、CUDA で必要なライブラリを指定するためにボリュームを使用する必要はありません。

apiVersion: apps/v1
kind: Deployment

metadata:
  name: tf-notebook
  labels:
    app: tf-notebook

spec:

  template: # define the pods specifications
    metadata:
      labels:
        app: tf-notebook

    spec:
      containers:
      - name: tf-notebook
        image: tensorflow/tensorflow:1.4.1-gpu-py3
        resources:
          limits:
            nvidia.com/gpu: 1

結論

Kubernetesがエコシステムでの地位を獲得したことで、拡張性はその主戦場となるでしょう。ヘテロジニアスコンピューティングは、Kubernetesにとって重要な新たな戦場となります。しかし、ヘテロジニアスコンピューティングには、強力なコンピューティングと高性能なネットワークが必要です。そのため、GPU、FPGA、NIC、InfiniBandなどの高性能なハードウェアと統一的に統合する必要があります。Kubernetes Device Pluginはシンプルで、そして今なお進化を続けています。Alibaba Cloud Container Serviceは、このDevice PluginをベースにしたKubernetes GPU 1.9.3クラスタの提供を開始します。

同様の記事を読んで、アリババクラウドの製品やソリューションについて詳しく知りたい方は、www.alibabacloud.com

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

bpftraceで Docker の仕組み調べてみる

概要

Dockerはよくdocker runとかdocker execのコマンドで Black Box みたいに使われて完結ですが、その中身をもっと詳しく調査してみたい。そのための有力ツールの一つはbpftraceです。

作業環境

  • Ubuntu 20.04

Dockerの構成

Dockerはおよそ以下のパーツに構成されている

  • docker-clidocker rundocker execなどのコマンドを提供するDockerのクライアント側
  • dockcerd:Docker のサーバー側、Docker Engine のデーモンプロセス、docker-cliなどクライアントからのリクエストを処理する場所
  • containerd:Container Runtime の一つ、いろんな Container の管理を機能しているデーモンプログラム
  • runc:いろんなOS機能を利用して、単独のContainerの実行を成せているツール

Dockerがやっていること

Containerは Namespace、Cgroup、OverlayFS、Virtual Network などのOS機能利用して可能になるのがよくご存知ですが、Docker はこちらの機能どう利用したますか?

まずは、Container が実行した結果を確認していきます。この一番簡単の Container 起動します

$ docker run -ti --rm --name test alpine sh

これからの利用のために pid と Container ID をに保存しておきます

$ export CPID=$(docker inspect test -f '{{ .State.Pid }}')
$ echo $CPID
230973
$ export CID=$(docker inspect test -f '{{ .Id }}')
$ echo $CID
b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0

containerdとの親子関係

$ pstree -s $CPID -STUpau
systemd,1
  └─containerd,80405
      └─containerd-shim,230955 -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd ...
          └─sh,230973,ipc,mnt,net,pid,uts

containerdcontainerd-shimを実行して、そしてまた Container プロセスを実行する。また、このContainer プロセスにはipcとかmntなどの namespace がついている。

runcの設定

containerdruncを利用してるので、その設定も確認します

$ jq -r '.process' < /var/run/containerd/io.containerd.runtime.v1.linux/moby/$CID/config.json
{
  "terminal": true,
  "user": {
    "uid": 0,
    "gid": 0,
  ...
  },
  "args": [
    "sh"
  ],
  "env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "HOSTNAME=b6431f14cf40",
    "TERM=xterm"
  ],
  "cwd": "/",
  "capabilities": {
    "bounding": [
      "CAP_CHOWN",
      "CAP_DAC_OVERRIDE",
      "CAP_FSETID",
      "CAP_FOWNER",
      "CAP_MKNOD",
      "CAP_NET_RAW",
      "CAP_SETGID",
      "CAP_SETUID",
      "CAP_SETFCAP",
      "CAP_SETPCAP",
      "CAP_NET_BIND_SERVICE",
      "CAP_SYS_CHROOT",
      "CAP_KILL",
      "CAP_AUDIT_WRITE"
    ],
    ...
  },
  "apparmorProfile": "docker-default",
  "oomScoreAdj": 0
}

Namespaces

Container プロセスについている namespace を具体的に見に行きましょう

$ lsns -p $CPID --output-all
        NS TYPE   PATH                NPROCS    PID   PPID COMMAND    UID USER NETNSID NSFS
4026531835 cgroup /proc/1/ns/cgroup      120      1      0 /sbin/init   0 root
4026531837 user   /proc/1/ns/user        120      1      0 /sbin/init   0 root
4026532186 mnt    /proc/230973/ns/mnt      1 230973 230955 sh           0 root
4026532187 uts    /proc/230973/ns/uts      1 230973 230955 sh           0 root
4026532188 ipc    /proc/230973/ns/ipc      1 230973 230955 sh           0 root
4026532189 pid    /proc/230973/ns/pid      1 230973 230955 sh           0 root
4026532191 net    /proc/230973/ns/net      1 230973 230955 sh           0 root       0 /run/docker/netns/904e1ae9696c

Cgroups

所属している Cgroup はこちら

$ cat /proc/$CPID/cgroup
12:freezer:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
11:devices:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
10:blkio:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
9:rdma:/
8:pids:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
7:memory:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
6:hugetlb:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
5:net_cls,net_prio:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
4:cpu,cpuacct:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
3:cpuset:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
2:perf_event:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
1:name=systemd:/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0
0::/system.slice/containerd.service

そして Cgroup の内容もちょっと見に行きましよう

$ cgget -g pids:/docker/$CID
/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0:
pids.current: 1
pids.events: max 0
pids.max: max
$ cgget -g net_cls,net_prio:/docker/$CID
/docker/b6431f14cf40267d3eed22b34fc6e974be28f2e0f5b9b2bfbccdffaa5327a4a0:
net_cls.classid: 0
net_prio.prioidx: 3
net_prio.ifpriomap: lo 0
    enp0s3 0
    docker0 0
    veth18981d6 0

Mounts

このContainer プロセスがマウントしたディレクトリはこちら

$ cat /proc/$CPID/mounts
overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/JVWDDYIF5YQFGNGL3PDNWJ4A4M:/var/lib/docker/overlay2/l/TQBVPLJN6SEMDRW7VD5FBARG4F,upperdir=/var/lib/docker/overlay2/ae3eab82db4efee497c2d69a4ad18a8cfc816ce0d61296fee7b6f611d7f6ebb3/diff,workdir=/var/lib/docker/overlay2/ae3eab82db4efee497c2d69a4ad18a8cfc816ce0d61296fee7b6f611d7f6ebb3/work,xino=off 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0
tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,relatime,mode=755 0 0
cgroup /sys/fs/cgroup/systemd cgroup ro,nosuid,nodev,noexec,relatime,xattr,name=systemd 0 0
cgroup /sys/fs/cgroup/perf_event cgroup ro,nosuid,nodev,noexec,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/cpuset cgroup ro,nosuid,nodev,noexec,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup ro,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
cgroup /sys/fs/cgroup/net_cls,net_prio cgroup ro,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup ro,nosuid,nodev,noexec,relatime,hugetlb 0 0
cgroup /sys/fs/cgroup/memory cgroup ro,nosuid,nodev,noexec,relatime,memory 0 0
cgroup /sys/fs/cgroup/pids cgroup ro,nosuid,nodev,noexec,relatime,pids 0 0
cgroup /sys/fs/cgroup/rdma cgroup ro,nosuid,nodev,noexec,relatime,rdma 0 0
cgroup /sys/fs/cgroup/blkio cgroup ro,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/devices cgroup ro,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup ro,nosuid,nodev,noexec,relatime,freezer 0 0
mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k 0 0
/dev/sda1 /etc/resolv.conf ext4 rw,relatime 0 0
/dev/sda1 /etc/hostname ext4 rw,relatime 0 0
/dev/sda1 /etc/hosts ext4 rw,relatime 0 0
devpts /dev/console devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
proc /proc/bus proc ro,relatime 0 0
proc /proc/fs proc ro,relatime 0 0
proc /proc/irq proc ro,relatime 0 0
proc /proc/sys proc ro,relatime 0 0
proc /proc/sysrq-trigger proc ro,relatime 0 0
tmpfs /proc/acpi tmpfs ro,relatime 0 0
tmpfs /proc/kcore tmpfs rw,nosuid,size=65536k,mode=755 0 0
tmpfs /proc/keys tmpfs rw,nosuid,size=65536k,mode=755 0 0
tmpfs /proc/timer_list tmpfs rw,nosuid,size=65536k,mode=755 0 0
tmpfs /proc/sched_debug tmpfs rw,nosuid,size=65536k,mode=755 0 0
tmpfs /proc/scsi tmpfs ro,relatime 0 0
tmpfs /sys/firmware tmpfs ro,relatime 0 0

ここで注目するのは overlay の情報

overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/JVWDDYIF5YQFGNGL3PDNWJ4A4M:/var/lib/docker/overlay2/l/TQBVPLJN6SEMDRW7VD5FBARG4F,upperdir=/var/lib/docker/overlay2/ae3eab82db4efee497c2d69a4ad18a8cfc816ce0d61296fee7b6f611d7f6ebb3/diff,workdir=/var/lib/docker/overlay2/ae3eab82db4efee497c2d69a4ad18a8cfc816ce0d61296fee7b6f611d7f6ebb3/work,xino=off 0 0

ここでマウントした/var/lib/docker/overlay2/l/TQBVPLJN6SEMDRW7VD5FBARG4Fは実は alpine イメージの内容です

$ cat $(docker image inspect alpine -f '{{ .GraphDriver.Data.UpperDir }}')/../link
TQBVPLJN6SEMDRW7VD5FBARG4F

ネットワーク

最後にネットワークの設定も確認しましよう。Container プロセスは独自の network namespace に区別しているから、host mahcineと通信のためには、vethを利用します。

まずは、Container側のネットワーク設定

$ nsenter -t $CPID -n ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 17  bytes 1366 (1.3 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
$ nsenter -t $CPID -n ip link show type veth
280: eth0@if281: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ nsenter -t $CPID -n ip route show dev eth0
default via 172.17.0.1
172.17.0.0/16 proto kernel scope link src 172.17.0.2

host machine 側

$ ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:ffff:fe14:4bd6  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ff:14:4b:d6  txqueuelen 0  (Ethernet)
        RX packets 8  bytes 433 (433.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 283  bytes 30567 (30.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
$ ip addr show type veth
281: veth18981d6@if280: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether fa:eb:12:ad:57:d1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::f8eb:12ff:fead:57d1/64 scope link
       valid_lft forever preferred_lft forever
$ ip route show dev docker0
172.17.0.0/16 proto kernel scope link src 172.17.0.1

Container のアドレスは172.17.0.2で、hostの方は172.17.0.1veth18981d6という veth で2つの interface を繋がている。そしてそれぞれの route table に転送経路のルールが載せている。

bpftraceでDocker実行順番を明白する

さて、いろいろの結果を確認してきたから、具体的に Docker はどうやってその結果のたどり着いたか、例えばいつにどんな関数をどんな変数で呼びましたか。ここではbpftraceが活躍するところです。

bpftrace っていうのは Linux Kenel の eBPF 機能を awk みたい記述で簡単に利用できるツールです。具体的な使用方法はここでは省きますが、Docに参照してください。

bpftraceのセットアップ

# bcc
$ apt-get install -y linux-headers-$(uname -r) bison build-essential cmake flex g++ git libelf-dev zlib1g-dev libfl-dev systemtap-sdt-dev binutils-dev llvm-8-dev llvm-8-runtime libclang-8-dev clang-8 arping netperf iperf3 python3-distutils
$ git clone --recurse-submodules https://github.com/iovisor/bcc.git
$ mkdir bcc/build; cd bcc/build
$ cmake -DPYTHON_CMD=python3 ..
$ make -j8 && make install && ldconfig

$ cd ../..

# bpftrace
$ git clone https://github.com/iovisor/bpftrace.git
$ mkdir bpftrace/build; cd bpftrace/build
$ cmake -DHAVE_BCC_PROG_LOAD=ON -DHAVE_BCC_CREATE_MAP=ON -DBUILD_TESTING=OFF ..
$ make -j8 && make install

Dockerをトレース

bpftraceで docker と OS のやり取りがトレースすることができる。まずはこちらの内容でdocker.btのファイル作成する

docker.bt
#!/usr/bin/env bpftrace

#include <linux/nsproxy.h>
#include <linux/ns_common.h>
#include <linux/utsname.h>
#include <linux/pid_namespace.h>
#include <linux/ipc_namespace.h>
#include <linux/cgroup.h>
#include <net/net_namespace.h>
#include <linux/netdevice.h>

BEGIN { 
    printf("%-12s %-15s %-8s %-10s %s\n", "TIME", "COMMAND", "PID", "ACTION", "CONTENT"); 
}

tracepoint:syscalls:sys_enter_execve { 
    printf("%-12ld %-15s %-8d %-10s ", elapsed , comm, pid, "execve");
    join(args->argv); 
}

tracepoint:syscalls:sys_enter_mount { 
    printf("%-12ld %-15s %-8d %-10s ", elapsed, comm, pid, "mount");
    printf("type=%s, dev=%s, dir=%s\n", str(args->type), str(args->dev_name), str(args->dir_name))
}

kretprobe:create_new_namespaces /comm=="runc:[1:CHILD]"/ { 
    printf("%-12ld %-15s %-8d %-10s ", elapsed, comm, pid, "namespace");
    $nsp = (struct nsproxy *)retval;
    printf("uts=%ld, ipc=%ld, cgroup=%ld, net=%ld, pid=%ld\n", 
        ((struct uts_namespace *)$nsp->uts_ns)->ns.inum,
        ((struct ipc_namespace *)$nsp->ipc_ns)->ns.inum,
        ((struct cgroup_namespace *)$nsp->cgroup_ns)->ns.inum,
        ((struct net *)$nsp->net_ns)->ns.inum,
        ((struct pid_namespace *)$nsp->pid_ns_for_children)->ns.inum);
}

kprobe:veth_newlink {
    printf("%-12ld %-15s %-8d %-10s ", elapsed, comm, pid, "veth");
    printf("name=%s, netns=%ld\n", ((struct net_device *)arg1)->name, ((struct net *)arg0)->ns.inum);
}

そして実行する

$ ./docker.bt
Attaching 5 probes...
TIME         COMMAND         PID      ACTION     CONTENT

もう一度 Container の起動する(別のターミナル)

$ docker run -ti --rm --name test alpine echo hi

docker.btに戻せば、こちらの内容出力されます

TIME         COMMAND         PID      ACTION     CONTENT
1827725094   bash            235026   execve     docker run -ti --rm --name test alpine echo hi
1874368200   dockerd         81563    mount      type=overlay, dev=overlay, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
1892118900   dockerd         81563    mount      type=overlay, dev=overlay, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
1904728454   dockerd         81563    mount      type=overlay, dev=overlay, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
1905395722   dockerd         81563    veth       name=veth3d71f6e, netns=4026531992
1912608987   networkd-dispat 235038   execve     /usr/bin/networkctl list --no-pager --no-legend
1913900866   (spawn)         235036   execve     /lib/udev/bridge-network-interface
1917882282   (spawn)         235039   execve     /lib/open-iscsi/net-interface-handler start
1922126799   (spawn)         235040   execve     /lib/udev/bridge-network-interface
1923736757   (spawn)         235041   execve     /lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/veth3d71f6e --prefix=/net/ipv4/neigh/veth3d71f6e --prefix=/net/ipv6/conf/veth3d71f6e --prefix=/net/ipv6/neigh/veth3d71f6e
1925374081   (spawn)         235042   execve     /lib/open-iscsi/net-interface-handler start
1935733149   containerd      235044   execve     containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
1940074927   containerd-shim 235053   execve     runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb/log.json --log-format json create --bundle /run/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb --pid-file /run/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb/init.pid --console-socket /tmp/pty046813817/pty.sock 6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb
1940243736   (spawn)         235043   execve     /lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/vethc30992d --prefix=/net/ipv4/neigh/vethc30992d --prefix=/net/ipv6/conf/vethc30992d --prefix=/net/ipv6/neigh/vethc30992d
1955997514   runc            235060   execve     runc init
1959669403   exe             235060   mount      type=, dev=/proc/self/exe, dir=/var/run/docker/runtime-runc/moby/6975e3344ad32b92a6a09066f3113
1962524451   exe             235060   mount      type=, dev=, dir=/var/run/docker/runtime-runc/moby/6975e3344ad32b92a6a09066f3113
1977110103   runc:[1:CHILD]  235061   namespace  uts=4026532187, ipc=4026532188, cgroup=4026531835, net=4026532191, pid=4026532189
2002233022   runc:[2:INIT]   235062   mount      type=, dev=, dir=/
2002647666   runc:[2:INIT]   235062   mount      type=bind, dev=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2002787106   runc:[2:INIT]   235062   mount      type=proc, dev=proc, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2002848509   runc:[2:INIT]   235062   mount      type=tmpfs, dev=tmpfs, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2003072500   runc:[2:INIT]   235062   mount      type=devpts, dev=devpts, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2006987497   runc:[2:INIT]   235062   mount      type=sysfs, dev=sysfs, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2007808775   runc:[2:INIT]   235062   mount      type=tmpfs, dev=tmpfs, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008001937   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/systemd/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008021032   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/systemd/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008102923   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/perf_event/docker/6975e3344ad32b92a6a09066f31132, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008116742   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/perf_event/docker/6975e3344ad32b92a6a09066f31132, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008189722   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/cpuset/docker/6975e3344ad32b92a6a09066f31132f9b4, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008203301   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/cpuset/docker/6975e3344ad32b92a6a09066f31132f9b4, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008284557   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/cpu,cpuacct/docker/6975e3344ad32b92a6a09066f3113, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008300251   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/cpu,cpuacct/docker/6975e3344ad32b92a6a09066f3113, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008365239   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/net_cls,net_prio/docker/6975e3344ad32b92a6a09066, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008397968   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/net_cls,net_prio/docker/6975e3344ad32b92a6a09066, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008471763   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/hugetlb/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008489627   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/hugetlb/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008575004   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/memory/docker/6975e3344ad32b92a6a09066f31132f9b4, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008588697   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/memory/docker/6975e3344ad32b92a6a09066f31132f9b4, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008655100   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/pids/docker/6975e3344ad32b92a6a09066f31132f9b4e3, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008667811   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/pids/docker/6975e3344ad32b92a6a09066f31132f9b4e3, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008731445   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/rdma, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008744138   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/rdma, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008792554   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/blkio/docker/6975e3344ad32b92a6a09066f31132f9b4e, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008805034   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/blkio/docker/6975e3344ad32b92a6a09066f31132f9b4e, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008855551   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/devices/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008868073   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/devices/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008919459   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/freezer/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008932321   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup/freezer/docker/6975e3344ad32b92a6a09066f31132f9b, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008958905   runc:[2:INIT]   235062   mount      type=bind, dev=/sys/fs/cgroup, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2008977832   runc:[2:INIT]   235062   mount      type=mqueue, dev=mqueue, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009006418   runc:[2:INIT]   235062   mount      type=tmpfs, dev=shm, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009102662   runc:[2:INIT]   235062   mount      type=bind, dev=/var/lib/docker/containers/6975e3344ad32b92a6a09066f31132f9b4e3, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009116671   runc:[2:INIT]   235062   mount      type=, dev=, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009152544   runc:[2:INIT]   235062   mount      type=bind, dev=/var/lib/docker/containers/6975e3344ad32b92a6a09066f31132f9b4e3, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009184117   runc:[2:INIT]   235062   mount      type=, dev=, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009256053   runc:[2:INIT]   235062   mount      type=bind, dev=/var/lib/docker/containers/6975e3344ad32b92a6a09066f31132f9b4e3, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2009268672   runc:[2:INIT]   235062   mount      type=, dev=, dir=/var/lib/docker/overlay2/ec1fbee3aa02f9596794e45afddaddf75028e0
2012566089   runc            235085   execve     libnetwork-setkey -exec-root=/var/run/docker 6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb ee4af79b8952
2055580696   dockerd         81563    mount      type=bind, dev=/proc/235062/ns/net, dir=/var/run/docker/netns/53666134ee2d
2058245033   dockerd         235093   execve     set-ipv6 /var/run/docker/netns/53666134ee2d all false
2117481953   (spawn)         235100   execve     /lib/open-iscsi/net-interface-handler stop
2135647000   runc:[2:INIT]   235062   mount      type=, dev=, dir=.
2135881831   runc:[2:INIT]   235062   mount      type=bind, dev=/dev/pts/0, dir=/dev/console
2135959898   runc:[2:INIT]   235062   mount      type=, dev=/proc/bus, dir=/proc/bus
2135974823   runc:[2:INIT]   235062   mount      type=, dev=/proc/bus, dir=/proc/bus
2135984627   runc:[2:INIT]   235062   mount      type=, dev=/proc/fs, dir=/proc/fs
2135995698   runc:[2:INIT]   235062   mount      type=, dev=/proc/fs, dir=/proc/fs
2136004311   runc:[2:INIT]   235062   mount      type=, dev=/proc/irq, dir=/proc/irq
2136017445   runc:[2:INIT]   235062   mount      type=, dev=/proc/irq, dir=/proc/irq
2136025919   runc:[2:INIT]   235062   mount      type=, dev=/proc/sys, dir=/proc/sys
2136036802   runc:[2:INIT]   235062   mount      type=, dev=/proc/sys, dir=/proc/sys
2136045583   runc:[2:INIT]   235062   mount      type=, dev=/proc/sysrq-trigger, dir=/proc/sysrq-trigger
2136057589   runc:[2:INIT]   235062   mount      type=, dev=/proc/sysrq-trigger, dir=/proc/sysrq-trigger
2136066994   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/asound
2136076692   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/acpi
2136145362   runc:[2:INIT]   235062   mount      type=tmpfs, dev=tmpfs, dir=/proc/acpi
2136198403   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/kcore
2136211095   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/keys
2136271650   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/latency_stats
2136281142   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/timer_list
2136292991   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/timer_stats
2136302265   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/sched_debug
2136312542   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/proc/scsi
2136411156   runc:[2:INIT]   235062   mount      type=tmpfs, dev=tmpfs, dir=/proc/scsi
2136458345   runc:[2:INIT]   235062   mount      type=, dev=/dev/null, dir=/sys/firmware
2136537743   runc:[2:INIT]   235062   mount      type=tmpfs, dev=tmpfs, dir=/sys/firmware
2325058622   containerd-shim 235102   execve     runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb/log.json --log-format json start 6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb
2334946680   runc:[2:INIT]   235062   execve     echo hi
2357752100   containerd-shim 235108   execve     /usr/bin/containerd --address /run/containerd/containerd.sock publish --topic /tasks/exit --namespace moby
2389193617   containerd-shim 235116   execve     runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v1.linux/moby/6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb/log.json --log-format json delete 6975e3344ad32b92a6a09066f31132f9b4e3d5c65e1b827ed91486f1128c17eb
2438983541   (spawn)         235129   execve     /lib/udev/bridge-network-interface
2441125682   networkd-dispat 235130   execve     /usr/bin/networkctl list --no-pager --no-legend
2441917196   (spawn)         235131   execve     /lib/open-iscsi/net-interface-handler start
2445708579   (spawn)         235132   execve     /lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/vethc30992d --prefix=/net/ipv4/neigh/vethc30992d --prefix=/net/ipv6/conf/vethc30992d --prefix=/net/ipv6/neigh/vethc30992d
2460630127   networkd-dispat 235133   execve     /usr/bin/networkctl list --no-pager --no-legend
2469750858   (spawn)         235134   execve     /lib/open-iscsi/net-interface-handler stop
2470070134   (spawn)         235135   execve     /lib/open-iscsi/net-interface-handler stop

これは Docker がこの Container を実行するためにやったことは一目瞭然です。簡単にまとめると、順番的にはこちらです

  • イメージの overlay をマウントする
  • veth などのネットワーク設定
  • containerd-shimを実行
  • runc init
  • namespaces 作り
  • cgroup などにいろんなマウント
  • runc start
  • ユーザーコマンド echo hi
  • runc delete
  • ネットワークの cleanup

参考資料

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

DevOpsアプローチに基づくAlibaba Cloud ECS上でのBoltのセットアップ

DevOpsアプローチを用いてAlibaba Cloud上にBoltを設置するために必要な手順をご紹介します。Boltは開発者向けにカスタマイズされた現代的なCMSです。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Boltについて

BoltはSilexの上に構築された現代的なCMSで、「"始めから正しく作られたWordpress"」とも言われています。素晴らしい基盤を持っているので、開発者にとっては良いCMSであると言えるでしょう。現在はSilexを使用したバージョン3ですが、SensioLabsが開発を停止しているため、v4からはSymfony 4を使用することになります。素晴らしいCMSがさらに良くなっていくことを期待しています。

Terraformについて

Terraformが発売されたのは2014年のことですから、ずいぶん前のことになりますね。Terraformが何なのか知らない人は、ぜひ知っておいた方がいいでしょう。Terraformは、HashiCorpが開発した infrastructure-as-code software です。データセンターのインフラを非常に高度な設定言語(この場合はHCL)で定義することができ、そこから任意のサービスプロバイダでインフラを構築するための詳細な実行計画を作成することができます。これにより、安全かつ予測可能なインフラストラクチャの作成、変更、改善が可能となり、ファイルをgitリポジトリにコミットしてバージョン管理を行うことができます。API を宣言的な設定ファイル (*.df) にコード化し、チームメンバー間で共有したり、コードとして扱ったり、編集したりレビューしたりすることができるオープンソースのツールです。

基本的には、設定ファイルに沿ってインフラストラクチャを作成します。「クラウドサービスのためのDocker」と考えることができます。しかし、Dockerfileの代わりにmain.tfがあります。

Puppetsのウェブサイトによると、Infrastructure-as-codeは、インフラストラクチャを管理するための現代的なアプローチであり、"DevOpsの基盤 "と呼ばれることもあります。

インフラをソフトウェアのように扱う:バージョン管理、継続的インテグレーション、コードレビュー、自動テストなど、ソフトウェア開発者が使用するのと同じツールやプロセスで管理できるコードとして扱います。これらにより、インフラストラクチャの変更をより簡単に、迅速に、安全に、そして確実に行うことができます。

Infrastructure as code は、バージョン管理、コードレビュー、継続的インテグレーション、自動テストなどの一般的な DevOpsプラクティスの前提条件です。これらのプラクティスにより、顧客を喜ばせる高品質のソフトウェアを継続的に提供することができます。

出典: https://puppet.com/solutions/infrastructure-as-code

BoltとTerraformの両方に精通しているので、チュートリアルを開始しましょう。

Terraformのインストール

Terraformのインストールはとても簡単です。必要なのはHomebrewだけです。Homebrewがインストールされていない場合は、こちらをご覧ください。

ターミナルで以下のコマンドを実行してTerraformをインストールします。

brew install terrafrom

Terraformのインストールを確認するには、以下のコマンドを入力します。

terraform version

アリババクラウド公式プロバイダーをインストール

このチュートリアルの中で一番トリッキーな部分なので、しっかりとやっておきましょう。HashiCorpから提供されているAlibaba Cloud用のプロバイダ(Alicloud Provider)は完璧とは程遠く、最新のものではありません。

Alibaba Cloudには、その公式プロバイダのGitHub repositoryが非常に優れていて、アクティブに開発されているので、それを入手してインストールしてください。releases tabに移動して、あなたのプラットフォームのための最新のものを取得します。

ダウンロードした後、バイナリファイルをterraformのpluginsフォルダに配置します。Windowsの場合は、ユーザーの "Application Data "ディレクトリの下にあるterraform.d/pluginsに置いてください。その他のシステム(Linux や Mac など)では、ユーザのホームディレクトリの ~/.terraform.d/plugins に置いてください。また、バイナリのバージョンを変更するのは良い習慣ですので、terraform-provider-alicloud_v1.6.0 にリネームしてください。

Alibaba Cloudのアクセスキーを取得する

Alibaba Cloudコンソールにログインしたら、上部のメニューからメールアドレスの直下にある「accessskeys」をクリックします。

image.png

鍵の画面が表示されたら、アクセスキーIDとアクセスキーの秘密を安全な場所にコピーしてください。秘密の鍵を表示するには、「表示」をクリックする必要があります。このデータは非常に機密性の高いデータなので、保存先には注意してください。また、ポリシーを使用して、より限定された鍵を作成することも検討してください。 

Terraformファイルの準備

この基本的な例では、すべてのコンフィグを一つのファイルにまとめますが、コンフィグの異なる部分をそれぞれの .tf ファイルに分けておくことをお勧めします。これは良い習慣で、時間が経つにつれて保守性と可読性が向上します。
フォルダを作成し、その中に main.tf というファイルを作成して、次のステップで編集します。

Variables

まずは変数について説明します。これらの変数は、Alibaba Cloud のインフラストラクチャに接続するために必要な情報を terraform に伝えるために使用します。main.tfファイルを開き、以下のコードを記述します。

variable "access_key" {
  type = "string"
  default = "XXXXX"
}
variable "secret_key" {
  type = "string"
  default = "XXXXX"
}
variable "region" {
  type = "string"
  default = "ap-southeast-2"
}
variable "vswitch" {
  type = "string"
  default = "XXX-XXXXX"
}
variable "sgroups" {
  type = "list"
  default = [
    "XX-XXXXX"
  ]
}
variable "name" {
  type = "string"
  default = "bolt-instance"
}
variable "password" {
  type = "string"
  default = "Test1234!"
}

ご覧のように、access_key, secret_key, region, vswitch id, そして sgroups のような、カスタマイズする必要のある変数がいくつかあります。vswitch については、ウェブパネルから ID を取得し、ECS を配置したい VSwitch を選択する必要があります。sgroups についても同様に、ECS を配置したいセキュリティグループの ID を取得する必要があります。しかし、これを動作させるためには、ポート 80、443、および 22 が公開されているグループに置く必要があることを覚えておいてください。

デフォルトでは、この例ではシドニーのデータセンターを使用しています。 

Provider

この部分は、Terraformにどのプロバイダを使用するかを伝えるだけなので、説明は簡単です。この例では、TerraformにAlibaba Cloud (Alicloud Provider)を使用するように指示しています。

provider "alicloud" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

Data

ここでは、使用するイメージとインスタンスの種類に関するECSの詳細を設定します。今回の例では、Ubuntu 16.04 64bits、1GB、1CPUを選択しています。しかし、ECSの仕様は用途に応じて必要に応じて増やすことができます。

data "alicloud_images" "search" {
  name_regex = "^ubuntu_16.*_64"
}
data "alicloud_instance_types" "default" {
  instance_type_family = "ecs.xn4"
  cpu_core_count = 1
  memory_size = 1
}

Resource

これが最も重要なブロックです。ここでは、最終的にどのようなリソースを作成するかをTerraformに伝えます。ここでは、先ほど定義した変数を使ってECSを作成しています。

イメージとしては、Ubuntu用に先に書いたregexを使って検索し、最初にAlibaba Cloudで公開されている最新版を選択しています。Instance Typeについても同様です。

resource "alicloud_instance" "web" {
  instance_name = "${var.name}"
  image_id = "${data.alicloud_images.search.images.0.image_id}"
  instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}"

  vswitch_id = "${var.vswitch}"
  security_groups = "${var.sgroups}"
  internet_max_bandwidth_out = 100
  allocate_public_ip = true

  password = "${var.password}"
}

remote-exec

先ほどの手順を終えても、ECSに何もインストールしなければあまり意味がありません。そのため、作成時にTerraformにSSHでログインしてもらい、dockerとdocker-composeをインストールする必要があります。その後、docker-composeの設定ファイルを取得して、roura/boltを使ってBoltのコピーをデプロイします。

そのためには、先ほど書いた最後のリソースブロックの中に、password = "${var.password}"の直後に以下のコードブロックを入れる必要があります。

provisioner "remote-exec" {
  inline = [
    "apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common",
    "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -",
    "add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"",
    "apt-get update && apt-get install -y docker-ce docker-compose",
    "curl https://raw.githubusercontent.com/roura356a/bolt/master/docker-compose.yml -o docker-compose.yml",
    "curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose",
    "docker-compose up -d"
  ]

  connection {
    host = "${alicloud_instance.web.public_ip}"
    password = "${var.password}"
  }
}

output

すべてが終わった後、生成したECSのIPがわからなければ、まだ先には進めません。そこで、以下のコードを実行して、TerraformにターミナルにIPを表示するように指示します。

output "ip" {
  value = "${alicloud_instance.web.public_ip}"
}

概要

結論から言うと、main.tfファイルは以下のようになります。

variable "access_key" {
  type = "string"
  default = "XXXXX"
}
variable "secret_key" {
  type = "string"
  default = "XXXXX"
}
variable "region" {
  type = "string"
  default = "ap-southeast-2"
}
variable "vswitch" {
  type = "string"
  default = "XXX-XXXXX"
}
variable "sgroups" {
  type = "list"
  default = [
    "XX-XXXXX"
  ]
}
variable "name" {
  type = "string"
  default = "bolt-instance"
}
variable "password" {
  type = "string"
  default = "Test1234!"
}

provider "alicloud" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

data "alicloud_images" "search" {
  name_regex = "^ubuntu_16.*_64"
}
data "alicloud_instance_types" "default" {
  instance_type_family = "ecs.xn4"
  cpu_core_count = 1
  memory_size = 1
}

resource "alicloud_instance" "web" {
  instance_name = "${var.name}"
  image_id = "${data.alicloud_images.search.images.0.image_id}"
  instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}"

  vswitch_id = "${var.vswitch}"
  security_groups = "${var.sgroups}"
  internet_max_bandwidth_out = 100
  allocate_public_ip = true

  password = "${var.password}"

  provisioner "remote-exec" {
    inline = [
      "apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common",
      "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -",
      "add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"",
      "apt-get update && apt-get install -y docker-ce docker-compose",
      "curl https://raw.githubusercontent.com/roura356a/bolt/master/docker-compose.yml -o docker-compose.yml",
      "curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose",
      "docker-compose up -d"
    ]

    connection {
      host = "${alicloud_instance.web.public_ip}"
      password = "${var.password}"
    }
  }
}

output "ip" {
  value = "${alicloud_instance.web.public_ip}"
} 

terraform init

飛び立つ準備が整いました!Terraformのinitコマンドを入力して、プロジェクトを適用する準備をします。

terraform init

terraform plan

問題がないことを確認するためには、planコマンドを実行しておくと、実際に適用しなくてもジョブの概要を知ることができます。

terraform plan

terraform apply

これでマシンをデプロイしました! applyコマンドを実行し、終了するまで待ちます。インターネットの接続状況や接続先のデータセンターの状況にもよりますが、3分~5分ほどかかります。
terraform apply

ジョブが終了すると、新しいECSインスタンスのIPアドレスを確認するメッセージがターミナルに表示されます。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

ip = XX.XX.XX.XX

選択したセキュリティグループがポート80を開いている場合は、ブラウザでIPを入力して、新しいWebサイトをカスタマイズするためのBoltのWebベースのインストールがどのように出てくるかを確認することができます。

これで正常にAlibaba Cloud ECS上にBoltをセットアップしました。それを使って何か楽しいことをしてみてください。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

[k8s]DockerHub の PrivateRegistry のイメージを Pod で利用する時のメモ

メモです。

DockerHub の PrivateRegistry を使う時の方法について確認した時のメモで以下を参考にやってみた

Pull an Image from a Private Registry

環境

$kubectl version
Client Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:26:26Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"15+", GitVersion:"v1.15.11-eks-af3caf", GitCommit:"af3caf6136cd355f467083651cc1010a499f59b1", GitTreeState:"clean", BuildDate:"2020-03-27T21:51:36Z", GoVersion:"go1.12.17", Compiler:"gc", Platform:"linux/amd64"}

前提確認

DockerHub の PrivateRegistry について確認する。

# PrivateRegistry なので認証なしでは Pull 出来ない
$docker pull toshihirock/privatenginx
Using default tag: latest
Error response from daemon: pull access denied for toshihirock/privatenginx, repository does not exist or may require 'docker login'

# docker login する
$docker login

# pull 出来た
$docker pull toshihirock/privatenginx

こんな感じ。
k8s でも確認。

# 応答がなくなる
$kubectl run -i private-test --image=toshihirock/privatenginx --generator=run-pod/v1 --rm --restart=Never --command -- echo 'hi'

# 別ウィンドウで見ると以下のように ImagePullBackOff になっている
$kubectl get pods |grep private-test
private-test                        0/1     ImagePullBackOff   0

やってみる

kubernetes.io/dockerconfigjson type のシークレットを作る

k8s で DockerHub の PrivateRegistry のイメージを使う方法として認証情報を予め kubernetes.io/dockerconfigjson タイプの `Secret として作成しておき、利用する方法がある。
docker login コマンドが成功している場合 ~/.docker/config.json に認証時に必要な設定があるのでこれを使う。

# ファイルの確認
$ cat ~/.docker/config.json
{
        "auths": {
                "https://index.docker.io/v1/": {
                        "auth": "xxxx"
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/18.06.1-ce (linux)"
        }
}

# json から kubernetes.io/dockerconfigjson タイプの Secret を作成
$kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
    --type=kubernetes.io/dockerconfigjson

Pod を起動してみる

kubectl run コマンドのオプションで imagePullSecrets の指定はできなさそうなのでマニュフェストを作って検証する。

# ある程度のテンプレを作る
$kubectl run private-test --image=toshihirock/privatenginx --generator=run-pod/v1 --restart=Never --dry-run -o yaml --command -- echo 'hi' > private-test.yaml

imagePullSecrets を追加して作成した Secret の regcred を指定する。
マニュフェストはこんな感じ。

private-test.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: private-test
  name: private-test
spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - command:
    - echo
    - hi
    image: toshihirock/privatenginx
    name: private-test
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

やってみる。

# マニュフェストを適用し、Pod の作成
$kubectl apply -f private-test.yaml

#  Pod の確認
$kubectl get pods |grep private-test
private-test                        0/1     Completed          0          100s

# echo 文が実行されていることを確認
$kubectl logs private-test
hi

無事成功

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

本当に簡単にDockerを理解する

ザックリとDockerの使用方法(コンテナの実行関係、イメージの作成)についてまとめました。これを読めば一通りはわかるよ! 1.Dockerって何? 1.1.コンテナ 仮想マシンと考えてください。Dockerではコンテナの上でイメージを実行します。 1.2.イメージ OSで実行するHDDのイメージと考えてみましょう。 1.3.コンテナとイメージを使うことで何が良いの? 結局、なんでDockerを使うのか?ですが、次のようなメリットがあります。以下では、それをDockerを触って理解していくことにしてみます。 いろいろなタイプのイメージを作成・管理できる。 イメージを共有ができ、同じ処理を様々なコンテナ上で実行できる。 つまり、複数の同じ構成を持ったサーバーマシンをすぐに用意できることができることがDockerのメリットです。 2.Dockerのコマンドを覚えよう。 まずはDockerの実行してみましょう! 2.1.準備インストール 皆さんの手持ちの環境で一番簡単に動かせると思うので、Docker for Windowsを使っていくことにしてみます。 Dockerにサインインしておきます。 2.2.Run(コンテナの実行) 何はともあれ、コンテナを実行したいと思います。 nginxというWebサーバーのイメージを起動してみましょう。 以下のコマンドで実行することができます。 > docker run nginx ただ、nginxはWebサーバーですので80番ポートを使用します。ですが、80番ポートはすでに使用されている場合が多いと思います。コンテナの80番ポートは、実機の5000番ポート上にポートを転送して実行してみましょう。(-p オプションを使用します。) >docker run -p 5000:80 nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx 68ced04f60ab: Pull complete 28252775b295: Pull complete a616aa3b0bf2: Pull complete Digest: sha256:2539d4344dd18e1df02be842ffc435f8e1f699cfc55516e2cf2cb16b7a9aea0b Status: Downloaded newer image for nginx:latest これで http://localhost:5000/にアクセスしてみましょう。 nginxが起動していることがわかりました。 Tips 今回は起動後、nginxサーバーをフォアグランドで実行し続けるイメージですので、コンテナは実行したままになります。イメージによっては起動後して処理を終えると、コンテナが終了してしまう場合もあります。コンテナの実行を維持したい場合は、 docker run -itd イメージ名 /bin/bash を試してみましょう。 2.3.実行中のコンテナの確認(PS) 次にどんなコンテナが実行されているか確認してみましょう。 > docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES aa8e7e1c8d67 nginx "nginx -g 'daemon of…" 39 minutes ago Up 39 minutes 0.0.0.0:5000->80/tcp funny_dijkstra コンテナIDにaa8e7e1c8d67(以降 aa)でnginxのコンテナが動作しているのがわかります。 2.4.実行中のコンテナで、コマンドの実行 実行中のコンテナでコマンドを実行してみます。 そのためにはexecを使います。また、実行先のコンテナを指定する必要があります。 >docker exec -it (コンテナ) コマンド コンテナ名は途中で省略してもよいです。 >docker exec -it aa ls bin dev home lib64 mnt proc run srv tmp var boot etc lib media opt root sbin sys usr また、以下のようにすれば、exitまでの期間、ターミナルを操作できますね。 >docker exec -it aa /bin/bash コンテナのシェルの中でnginxの設定ファイル(/etc/nginx/conf.d/default.conf)の中を見てみましょう。dockerは最小限しかインストールしないものなので、viが入っていません。catを使う必要がありますね。 root@aa8e7e1c8d67:cat /etc/nginx/conf.d/default.conf server { listen 80; server_name localhost; ... location / { root /usr/share/nginx/html; index index.html index.htm; } ... } なるほど、/usr/share/nginx/htmlがコンテンツ置き場のようですね。 最後はexitしてシェルを抜けます。 >....(いろいろなコマンド操作) >exit これで大体の操作の感じをつかめたのではないでしょうか。 3.コンテナイメージを作ろう それでは自分用のコンテナイメージを作っていきましょう。 Dockerの利点は、様々なバリエーションのイメージを簡単に管理できることにあります。 DockerのイメージはDockerfileで作成しますので、まずはDockerfileを編集します。 3.1.Dockerfileの作り方 今回は次の操作をしてみましょう。Webサーバーに提供されるコンテンツです。 このイメージは、nginxが動作しているよ!と言いたいイメージとします。 以下のhello.htmlを表示でいるようにしたいと思います。 hello.html <!DOCTYPE html> <html> <head> </head> <body> <h1>Hello World</h1> </body> </html> # nginxイメージを起点にする FROM nginx # vimのインストール RUN apt-get update RUN apt-get -y install vim # hello.htmlファイルをコンテナに追加 ADD hello.html /usr/share/nginx/html # コンテナ実行時にnginxを実行 CMD ["/usr/sbin/nginx", "-g", "daemon off;"] よく使用するコマンドをまとめておきます。 コマンド 意味 FROM 派生元のイメージを指定する RUN イメージをビルドする際の処理 ADD イメージに指定のファイルを追加する。アーカイブファイルの場合は展開する COPY イメージに指定のファイルを追加する。 CMD イメージを実行する際に実行するコマンド ENTORYPOINT イメージ実行時に実行するコマンド、docker run時に上書き不可 Dockerfileができたら、ビルドをします。 空のディレクトリを作り、Dockerfileとhello.htmlにしてください。 >mkdir my_nginx_dockerfiles # 空ディレクトリを作り >copy Dockerfile my_nginx_dockerfiles   # Dockerfileと >copy hello.html my_nginx_dockerfiles # 今回イメージに追加するファイルだけを入れます。 >cd my_nginx_dockerfiles # ディレクトリに移動します >docker build -t my_nginx:0.1 .      # ビルドです  Sending build context to Docker daemon 3.072kB ... Successfully built 1e53c2c84fba Successfully tagged my_nginx:0.1 SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories. 最後にパーミッション関係で指摘がありますが、成功ですね。image listコマンドでイメージができたか確認します。 >docker image list REPOSITORY TAG IMAGE ID CREATED SIZE my_nginx 0.1 1e53c2c84fba 7 minutes ago 178MB nginx latest 6678c7c2e56c 10 days ago 127MB さぁ、さっそく実行してみましょう。 >docker run -p 5000:80 my_nginx:0.1 追加したファイル(http://localhost:5000/hello.html)にアクセスしてみましょう。 カスタムイメージの完成です! 4.コンテナやイメージを管理しよう。 4.1. Push / Pull Dockerではイメージを共有することができます。非常に素晴らしい機能です。 Pull イメージを取得します。 > docker pull tensorflow/tensorflow 4.1. 不要コンテナ、イメージの削除 コンテナやイメージを管理しましょう。 コンテナはdocker psコマンドで、イメージはdocker image listコマンドで一覧できます。 Exitされたコンテナは不要ですので、rmで削除することができます。 また、同様にカスタムイメージを作り終わったので、nginxも不要です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerでプロセスをきれいに終了したい

Dockerでプロセスをきれいに終了したい

EntrypointがShell Scriptの時、子プログラムを正しく終了させたい。

このようなことを実現する方法です。

前回、Pythonで終了時に必ず何か実行したい で、Pythonのプログラムをkillで停止されても、正しくCleanupするようにする方法について書きました。
そのようなきちんとCleanup処理が書かれているプロクラムをDockerで実行したとき、docker stopで正しくClecnupされる方法について見てみます。

直接Entrypointで実行する場合

main_process.pyという正しくCleanupされるように書かれたプログラムを直接Entrypointに指定してコンテナを作成してみます。
前回 Pythonで終了時に必ず何か実行したい で作成したPythonプログラムを使用します。

Dockerfile
FROM alpine

RUN apk add python3

WORKDIR /
COPY main_process.py /
RUN chmod +x /main_process.py

ENTRYPOINT /main_process.py
# docker build -t cleanup .

実行してみます。

# docker run -it --rm --name cleanup cleanup
!!!Set up!!!

別のターミナルからコンテナを停止します。

# docker stop cleanup
!!!Clean up!!!

ちゃんと、Cleanup処理が走りました。
dockerはEntrypointで指定したプログラムをProcess番号1として起動します。
そして、docker stopは、Process番号1に対してkillを実行します。

mail_process.pyはSIGTERMが来るとClean up!!と表示するようになっているので、このように正しく動作しました。

Shell Script経由で実行する場合

単純に実行する

上記のように、直接Entrypointで指定できれば楽なのですが、環境変数から設定ファイルをゴニョゴニョしてからメインのプログラムを実行するという場合もよくあります。
このような場合の動きを確認してみましょう。

main_process.pyを実行するentry.shを作成して、Entrypointにはentry.shを指定します。

Dockerfile
FROM alpine

RUN apk add python3

WORKDIR /
COPY main_process.py /
COPY entry.sh /
RUN chmod +x /main_process.py
RUN chmod +x /entry.sh

ENTRYPOINT /entry.sh
entry.sh
#!/bin/sh

/main_process.py

これをbuildして実行し、さっきと同じようにdocker stopを実行すると、docker stopコマンドが終了しません。
10秒ほどすると、docker stopコマンドが終了し、docker runも同時に終了します。

この時のdocker run側のターミナルは、

# docker run -it --rm --name cleanup cleanup
!!!Set up!!!
#

のような状態で、残念ながら、!!!Clean up!!!は表示されません。

10秒はdockerでstopができなかった場合のtimeoutで、dockerはSIGTERMを投げた後、10秒待っても終了しなければ、SIGKILLを投げて強制終了させます。

execを使用する (解決方法1)

entry.shを以下のように変えて、entry.shのプロセス自身をmain_process.py(正確にはpython3)に置き換えます。

entry.sh
#!/bin/sh

exec /main_process.py

これをbuildして実行し、さっきと同じようにdocker stopを実行すると、今度は、すぐに終了します。

# docker run -it --rm --name cleanup cleanup
!!!Set up!!!
!!!Clean up!!!
#

docker run側のターミナルには、ちゃんとCleanupが表示されています。

execのあと、main_process.pyがプロセス番号1に置き換わっているので、当然といえば当然です。
ただし、終了処理に10秒以上かかるとSIGKILL(kill -9)されてしまうので、Cleanup処理は10秒以内に終わらせることが重要です。

entry.shでもSIGTERMをトラップする (解決方法2)

entry.sh
#!/bin/sh

handler() {
    kill ${child_pid}
    wait ${child_pid}
}

trap handler SIGTERM

/main_process.py &
child_pid=$!

wait ${child_pid}

子プロセスはバックグラウンドで動作させるようにし、waitで終了を待ちます。
一方で、SIGTERMをトラップし、entry.shがSIGTERMを受け取った場合には、子プロセスをkillし、終了を待つようにします。

先ほどと同様に、実行して、docker stopdしてみると、

# docker run -it --rm --name cleanup cleanup
!!!Set up!!!
!!!Clean up!!!

正しく、Cleanup処理できていることが確認できます。
こちらも、Cleanup処理は10秒以内に終わらせることが重要です。

また、handler()の中身を書き換えれば、子プロセスの終了だけでなく、データをPersistentな領域に移動する、というようなことも可能です。(10秒制限ありますが。)

【応用】entry.shから複数の子プロセスの終了を待つようにする

SIGTERMをトラップする方法ではプロセスが1つの場合でも実行をバックグラウンドで行いました。
これを応用すると、複数のプロセスの終了を正しく待つことができそうですので、やってみます。

entry.sh
#!/bin/sh

handler() {
    kill ${child_pid1}
    kill ${child_pid2}
    wait ${child_pid1}
    wait ${child_pid2}
}

trap handler SIGTERM

/main_process1.py &
child_pid1=$!

/main_process2.py &
child_pid2=$!

wait ${child_pid1}
wait ${child_pid2}

2つ並べるだけです。
簡単ですね。

同様にテストしてみます。

# docker run -it --rm --name cleanup cleanup
!!!Set up!!!
!!!Set up!!!
!!!Clean up!!!
!!!Clean up!!!
#

2つのプログラムが正しくCleanup処理を行った後終了したことが確認できました。

 まとめ

EntrypointにShell Scriptを使う場合、正しく子プロセスを終了させる方法は、

  • execでプロセスを置き換える
  • SIGTERMをトラップして、子プロセスにSIGTERMを送って待つ

の二通りのやり方があることがわかりました。

*本記事は @qualitia_cdevの中の一人、@hirachanさんに書いて頂きました。

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

Docker × Java シンプルすぎる開発環境構築

DockerでJavaの開発環境構築

概要

Docker上にJavaのコンテナを設置するだけのシンプルな環境を構築します。
プログラムを極めし証Hello World!をコンソールに出力するところまでをご紹介します(笑)

環境

  • macOS Catalina バージョン10.15.5
  • Docker version 19.03.8
  • docker-compose version 1.25.5

構成

最終的に以下のような構成になります。

├── docker
│   └── java
│       └── Dockerfile
├── docker-compose.yml
└── server
    └── src
        ├── Main.class
        └── Main.java

手順

1. docker-compose.yml作成

javaコンテナ1つだけのシンプルな構成です。

docker-compose.yml
version: '3.6'
services:
  java:
    build: ./docker/java
    ports:
      - 8080:8080
    tty: true
    volumes:
      - ./server/src:/usr/src:cached

2. Dockerfile作成

Dockerfile
FROM openjdk:11-slim

RUN apt-get update
WORKDIR /usr/src

3. テストファイルを作成

Main.javaというテストファイルを作成します。

Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

4. Docker起動


// dockerビルド
% docker-compose build

// dockerをバックグラウンドで起動
% docker-compose up -d

// 確認
% docker-compose ps
       Name          Command   State           Ports         
-------------------------------------------------------------
java-spring_java_1   jshell    Up      0.0.0.0:8080->8080/tcp

5. コンパイルと実行

// インスペクション
% docker-compose exec java bash

// コンパイル
root@5b7be900c329:/usr/src# javac Main.java

// 実行
root@5b7be900c329:/usr/src# java Main
Hello World!

参考

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

DockerでMongoDBコンテナ起動時に初期データをインポートする

はじめに

DockerでMongoDBをコンテナを立ち上げ、データを確認するまでの最小限の構成になります。

やりたいこと

  • コンテナ作成時に初期データを投入したい
  • 投入したデータをGUIクライアントで参照したい

ディレクトリ構成

.
├── docker-entrypoint-initdb.d
│   ├── 1-mongo-init.js
│   ├── 2-mongo-init.sh
│   └── staffs.json
└── docker-compose.yml

各ファイルの内容

docker-compose.yml

「MONGO_INITDB_DATABASE」に指定した名称でデータベースが作成されます。

docker-compose.yml
version: '3.3'

services:
  mongo:
    image: mongo
    container_name: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongo
      MONGO_INITDB_ROOT_PASSWORD: mongo
      MONGO_INITDB_DATABASE: mongo_example
      TZ: Asia/Tokyo
    ports:
      - 27018:27017
    volumes:
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d

docker-entrypoint-initdb.d/1-mongo-init.js

ユーザの作成と合わせて、コレクションの作成をしています。
ここでは例としてstaffsコレクションを作成します。

docker-entrypoint-initdb.d/1-mongo-init.js
var user = {
  user: "mongo",
  pwd: "mongo",
  roles: [
    {
      role: "dbOwner",
      db: "mongo_example"
    }
  ]
};

db.createUser(user);
db.createCollection('staffs');

docker-entrypoint-initdb.d/2-mongo-init.sh

作成したユーザ、パスワード、データベース、コレクションと、インポートするJSONファイルを指定します。

docker-entrypoint-initdb.d/2-mongo-init.sh
mongoimport -u mongo -p mongo --db mongo_example --collection staffs --file /docker-entrypoint-initdb.d/staffs.json --jsonArray

docker-entrypoint-initdb.d/staffs.json

staffsコレクションにインポートするデータをJSONファイルで用意します。

docker-entrypoint-initdb.d/staffs.json
[
    {
        "id" : 1,
        "name" : "佐藤"
    },
    {
        "id" : 2,
        "name" : "鈴木"
    },
    {
        "id" : 3,
        "name" : "田中"
    }
]

実行

docker-composeで起動します。

$ docker-compose up -d
Creating network "mongodb_default" with the default driver
Creating mongo ... done

GUIクライアントで確認する

1. MongoDB Compassをダウンロード

MongoDB CompassからPlatformを選択しDownloadボタンをクリックします。

2. 接続

Paste your connection stringに以下の接続情報を入力し、CONNECTボタンをクリックします。

mongodb://mongo:mongo@localhost:27018

スクリーンショット 2020-07-13 1.31.20.png

3. 結果

staffコレクションにデータをインポートできていることが確認できます。

スクリーンショット 2020-07-13 1.51.39.png

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

DockerでMongoDBコンテナ起動時に初期データをインポートする

はじめに

DockerでMongoDBをコンテナを立ち上げ、データを確認するまでの最小限の構成になります。

やりたいこと

  • コンテナ作成時に初期データを投入したい
  • 投入したデータをGUIクライアントで参照したい

ディレクトリ構成

.
├── docker-entrypoint-initdb.d
│   ├── 1-mongo-init.js
│   ├── 2-mongo-init.sh
│   └── staffs.json
└── docker-compose.yml

各ファイルの内容

docker-compose.yml

「MONGO_INITDB_DATABASE」に指定した名称でデータベースが作成されます。

docker-compose.yml
version: '3.3'

services:
  mongo:
    image: mongo
    container_name: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongo
      MONGO_INITDB_ROOT_PASSWORD: mongo
      MONGO_INITDB_DATABASE: mongo_example
      TZ: Asia/Tokyo
    ports:
      - 27018:27017
    volumes:
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d

docker-entrypoint-initdb.d/1-mongo-init.js

ユーザの作成と合わせて、コレクションの作成をしています。
ここでは例としてstaffsコレクションを作成します。

docker-entrypoint-initdb.d/1-mongo-init.js
var user = {
  user: "mongo",
  pwd: "mongo",
  roles: [
    {
      role: "dbOwner",
      db: "mongo_example"
    }
  ]
};

db.createUser(user);
db.createCollection('staffs');

docker-entrypoint-initdb.d/2-mongo-init.sh

作成したユーザ、パスワード、データベース、コレクションと、インポートするJSONファイルを指定します。

docker-entrypoint-initdb.d/2-mongo-init.sh
mongoimport -u mongo -p mongo --db mongo_example --collection staffs --file /docker-entrypoint-initdb.d/staffs.json --jsonArray
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SSRなNuxt.jsを環境ごとにDocker化する時の環境変数の扱いについて

やりたいこと

nuxt.config.jsで環境変数をセットしたい。
本番環境でも動作するSSRなDocker Imageを作りたい。

結論

nuxt.jsを本番環境用にdocker化するには、.envを使わずに

  1. CIないし、ローカルマシンなど、docker buildする環境でで、環境変数を全て展開(export)する
  2. 展開したら yarn build を実行する
  3. .nuxt をCOPYする形で docker build してimageを作る

経緯

:x: 手法1: Dockerfileのentrypointを yarn start にして、docker-compose等で環境変数を渡す

例えば、nuxt.jsでaxiosを使う場合、APIサーバーなどへのリクエストする時にはbaseURLの設定をする必要がある。
baseURLは環境変数を用いて適用したくなることもある。

ここでnuxt.jsを本番環境で公開しようとした時のステップを整理すると

$ yarn build && yarn start

のように、一度buildして、そのbuildしたデータ(.nuxtディレクトリ)を使って、startすることでserveできるようになっている。

参考: API: env プロパティ - NuxtJS

nuxt.jsでは、このbuild時に環境変数を全て展開した上で、.nuxtを作成するようになっている。


例: HELLO=world という環境変数がセットされていた時
console.log(process.env.HELLO) は 具体的な console.log('world') という環境変数の中身によって置き換わったjsファイルが生成されることになる。


そのため、build時にはすでに環境変数がセットされている必要がある。
逆に言えば、startのタイミングで環境変数を読み込んでいるわけではない、ということである。

ということは、axiosなどのbaseURLを環境変数で指定する場合、buildの時点で環境変数がセットされている必要があることになる。

その前提でいうと、環境変数の読み込みは、yarn start時では遅いということになる。

なので、entrypointを指定する形のdockerfileで、外から環境変数を与えて起動しても、タイミング的に環境変数は.nuxtに適用されない。

FROM node:10-alpine

WORKDIR /app
ENV NUXT_HOST 0.0.0.0

COPY ./nuxt .
RUN yarn install && yarn build #この時点で環境変数が埋め込まれ済み

EXPOSE 3000

ENTRYPOINT ["yarn"]
CMD ["start"] # この時点では、例えばNUXT_HOSTは文字列の'0.0.0.0'に直接置き換わっている

じゃあnuxtをcopyした時に、.envを読み込ませればいいか?というと、またそうでもない問題が発生する。
あと、.envファイルはimageにあんまり載せたくない気持ちもある。

:x: 手法2: dotenvを使って環境変数を読み込む

一旦、doetnvを使って環境変数を読み込んでみた。
すると、なんとbaseURLは空になった。

いろいろ検証してみると、どうやらyarn buildの時に、読み込まれる順番として

  1. exportされた環境変数が、 process.env に読み込まれる
  2. nuxt.config.js内での環境変数呼び出し部分に値が入る
  3. nuxt.config.jsの中のenvオブジェクトに値が入る( env: { baseURL: process.env.BASE_URL } などの記述)
  4. dotenvファイルの読み込みが発生して、このタイミングで、 process.env.BASE_URL などの値がセットされる
  5. clientのjsファイルが、 process.env の値を具体的な値に置き換えた状態で生成される

という順番になる様子。

つまり、 dotenvはnuxt.config.jsの中の環境変数呼び出しには間に合わない ということになる。(少なくとも再現時はそうでした)

なので、nuxt.config.jsの中で環境変数を展開して欲しいタイミングでは、dotenvはまだ読み込まれていない状態となる。

:o: 手法3: .nuxtの生成はCIかローカルマシンで行う

結局この方法をとった、もしかしたら常識だったかもしれないし、もっといい方法があるかもしれないけど、あんまり経験がなかったのでこの方法で実装することにした。

結論と同じだが、環境変数の読み込みはyarn buildするよりも前に実行する必要がある。
そのため、buildする環境では予め環境変数をセットしておいて、その中でbuildを行うことにする。

なので、ローカルマシンでyarn buildまでは実行しておいてその.nuxt込みでDockerfileにCOPYするようにする。

すると、docker buildしてできたdocker imageは、yarn startするだけでproduction環境として実行可能なnuxt.jsサーバーとなる。

それが結論に書いた方法になる。

build環境で環境変数を予めセットしておかないといけないという煩わしさは、ローカルマシンでbuildできる環境が必要ということはあるものの
いったんこの方法に落ち着きましたので、忘備録として残しておきます。

誰かの役に立つと良いなーと思います、もっといい方法があれば是非教えてください!

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

Flutterでも使われているGoogleが開発している2DグラフィックライブラリのSkiaを試してみる

Skiaとは?

公式ページ

Skia(スキア)とは、Google が開発している、C++ で書かれたクロスプラットフォームかつ
オープンソースの2次元コンピュータグラフィックスライブラリ

Wikipediaより

Flutterでも使用されている!!。

:computer:環境構築


他の言語同様、メジャーなライブラリそうなので、Conan (パッケージマネージャー)を使って
Skiaを導入できるものかと思ってましたが、
conan search "skia" --remote=conan-center で検索しても見つからず... (探し方が悪い?)

色々調査したのですが、地道に1から環境を構築した方が早そうなので環境構築からやっていきたいと思います。
ベースとなる環境はこちらの環境を流用して構築していきます。
まずはDockerfileを以下で用意します。

FROM silkeh/clang:latest

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

RUN apt-get update && \
    apt-get install -y \
      git \
      python \
      curl \
      build-essential \
      libfontconfig-dev \
      libgl1-mesa-dev \
      libglu1-mesa-dev \
      libxi-dev \
      vim \
      cmake \
      python-pip \
      --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip setuptools && pip install conan

RUN cd /opt \
 && git clone 'https://chromium.googlesource.com/chromium/tools/depot_tools.git' \
 && git clone 'https://skia.googlesource.com/skia.git'

ENV PATH="/opt/depot_tools:${PATH}"

RUN cd /opt/skia \
  && python tools/git-sync-deps \
  && gn gen out/Static --args='is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false target_cpu="x64" extra_cflags_cc=["-frtti"]' \
  && ninja -C out/Static

ADD . /usr/src/app

CMD clang++

ベースにした環境のDockerfileからの差分としては、skia 本体やビルド時に使用する depot_tools をcloneする為の
gitcurl, ビルドで使用する libxxx を追加しています。
実際にビルドしている箇所は

RUN cd /opt/skia \
  && python tools/git-sync-deps \
  && gn gen out/Static --args='is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false target_cpu="x64" extra_cflags_cc=["-frtti"]' \
  && ninja -C out/Static

こちらで依存関係のモジュール等をcloneしビルド設定 + ビルド実行を行っています。

  • PythonのVersion

    $ python --version
    Python 2.7.16
    

docker build して /opt/skia/out/Staticlibskia.a ができていれば成功です :sparkles:

:pencil: 実装


skia はbackend(描画対象)として以下が選択できます。
- Raster - CPU-only.
- GPU - Skia's GPU-accelerated backend.
- SkPDF - PDF document creation.
- SkPicture - Skia's display list format.
- NullCanvas - Useful for testing only.
- SkXPS - Experimental XPS backend.
- SkSVG - Experimental SVG backend.

描画対象を選択できるのは便利ですね :sparkles:
まずはCPUのみで描画する Raster を使用するサンプルを試してみたいと思います。

Raster- 簡単なサンプル

#include <string>
#include <fstream>
#include <iostream>
#include "spdlog/spdlog.h"

#include "SkCanvas.h"
#include "SkData.h"
#include "SkEncodedImageFormat.h"
#include "SkImage.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkSurface.h"

// https://skia.org/user/api/skcanvas_overview こちらのサンプルを少し修正
void draw(SkCanvas* canvas) {
  const SkScalar scale = 256.0f;
  const SkScalar R = 0.45f * scale;
  const SkScalar TAU = 6.2831853f;
  SkPath path;
  path.moveTo(R, 0.0f);
  for (int i = 1; i < 7; ++i) {
      SkScalar theta = 3 * i * TAU / 7;
      path.lineTo(R * cos(theta), R * sin(theta));
  }
  path.close();
  SkPaint p;
  p.setAntiAlias(true);
  p.setStyle(SkPaint::kFill_Style);
  p.setColor(SK_ColorBLUE);
  canvas->clear(SK_ColorWHITE);
  canvas->translate(0.5f * scale, 0.5f * scale);
  canvas->drawPath(path, p);
}

int main() {
  spdlog::info("start skia sample");

  // 描画対象キャンバスの準備
  sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(640, 480);
  SkCanvas *canvas = surface->getCanvas();
  canvas->clear(SK_ColorWHITE);

  // 描画
  draw(canvas);

  // 画像の保存
  sk_sp<SkImage> image(surface->makeImageSnapshot());
  sk_sp<SkData> data(image->encodeToData(SkEncodedImageFormat::kPNG, 100));

  std::ofstream ofs("sample.png", std::ios::binary);
  ofs.write(reinterpret_cast<const char*>(data->data()), data->size());
  ofs.close();

  spdlog::info("end skia sample");
  return 0;
}

上記のコードをビルドする為に、CMakeLists.txt を以下の内容で作成します。

cmake_minimum_required(VERSION 3.13.4)
project(SkiaSample)
find_package (Threads)

include_directories(
  /opt/skia
  /opt/skia/include/core
  /opt/skia/include/config
  /opt/skia/include/utils
  /opt/skia/include/gpu
  /opt/skia/include/docs
)

add_definitions("-std=c++14")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

link_directories(
  /opt/skia/out/Static
)

add_executable(sample sample.cpp)
target_link_libraries(sample
  skia
  z
  dl
  fontconfig
  freetype
  GL
  GLU
  ${CMAKE_THREAD_LIBS_INIT}
  ${CONAN_LIBS}
)

ついでにビルド用のシェルも作成します。
ビルド用のシェルでは CC, CXX をClangを使う様に設定し、
conanのinstallも行っています。

#!/usr/bin/env bash

export CC=/usr/local/bin/clang
export CXX=/usr/local/bin/clang++

if [ -e build ]; then
  rm -rf build
  mkdir build
else
  mkdir build
fi

cd build
conan install .. --build=fmt --build=spdlog -pr=../clang_profile
cmake ..
make

いざビルドして、成功すると ./build/bin/sample が作成されているので

$ ./build/bin/sample

実行して、以下の様な画像の sample.png が作成されれば成功です。
image.png

SkPDF- 簡単なサンプル

次に PDFを描画対象とする様に修正してみたいと思います。
とはいえ、sample.cpp を以下に修正してビルド + 実行すれば↑と同じ画像のpdfが作成される様になります :sparkles:

...
// 以下のincludeを追加
#include "SkPDFDocument.h"
#include "SkStream.h"

// main関数を以下に修正
int main() {
  spdlog::info("start skia sample");

  // 描画対象キャンバスの準備
  SkFILEWStream pdfStream("sample.pdf");
  auto pdfDoc = SkPDF::MakeDocument(&pdfStream);
  SkCanvas* pdfCanvas = pdfDoc->beginPage(SkIntToScalar(640),
                                          SkIntToScalar(480));
  // 描画
  draw(pdfCanvas);

  pdfDoc->close();
  spdlog::info("end skia sample");
  return 0;
}

その他

skiaには他にも面白そうなものがあるので、時間があれば試してみたい :sparkles:
- Lottie Animation PlayerのSkottie
- Skia + WebAssembly - CanvasKit
- Geometry in the Browser - PathKit
- SkSL ("Skia Shading Language")

:bomb: バッドノウハウ


:sparkles: 感想


  • Skia が思ってた以上にできる事が多く、時間があれば色々試してみたいと思いました。
  • Flutter engineでどの様な使われ方をしているかも調べてみよう...

:link: 参考になったURL


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