20191209のdockerに関する記事は26件です。

Windows 10 HomeのWSL2でdocker-composeを使う

この記事はニフティグループ Advent Calendar 2019の10日目の記事です。


入社1年目研修の一環としてLaravelフレームワークを使ってのチーム開発を行いました。
せっかくならば家でもLaravelで開発を行おうと思い、研修時にも使ったLaradockを用いて環境構築を行おうと考えました。

ところがちょっと待てよ、自分のPCはWindowsじゃないか。
Dockerを動かすのがかなり面倒だと聞いたぞ。
VM立てたりしなきゃいけないのか…?

やる気を削がれつつ調べてみると、WSL2を用いてDocker(docker-compose)が使えるようだったので、そちらを採用して環境構築を進めていきました。

環境

  • Windows 10 Home
    • バージョン 1903
    • OSビルド 18362.418

WSL2の導入

WSL2は2019/12/08現在は正式リリースされておらず、2020年の春に正式リリースされるのではと噂されていたりします。
現段階でWSL2を使うためには、OSビルドの先行リリースをプレビューできるWindows Insider Programに登録し、β版として使う必要があります。

Windows Insider Programの利用を開始する

WSL2を使うための要件を一読し、公式サイトにしたがってWindows Insider Programの利用を開始します。
要約すると以下のような感じです。

  1. Microsoftの公式ページから自身のMicrosoftアカウントをInsiderとして登録。
  2. 管理者権限のあるユーザーで、スタート>設定>更新とセキュリティ>Windows Insider Programから開始するをクリック。
  3. アカウントを選んで開始からWindows Insider Programに登録したMicrosoftアカウントを入力。
  4. 受信したいコンテンツの種類と受信頻度を適切に選択し、PCを再起動。

WSL2を使うための要件では受信頻度を"ファスト"にせよという指示がありますが、11/17時点では"スロー"でも必要なビルドを受信することができました。
"ファスト"はややリスクが大きいですので、"スロー"を選択することを推奨します。

また、私の環境の場合、ビルドの受信とインストールには数時間かかりました。
マシンスペックとネットワークの状況によりけりかとは思いますが、時間の余裕のある時に行いましょう。

スタート>設定>システム>バージョン情報にあるWindowsの仕様の項目から、インストール済みのビルドを確認できます。

image.png

ビルドが18917以降になっていれば準備完了です。

WLS1を有効化する

WSL2はWSL1をアップグレードすることで使えるようになります。
なので、あらかじめWSL1を使える状態にしておきます。

  1. スタート>設定>アプリ>アプリと機能関連項目からプログラムと機能をクリック。
  2. 左の一覧からWindowsの機能の有効化または無効化をクリック。
  3. 以下の3つを有効化して再起動します。
    • Linux用Windowsサブシステム
    • Windowsハイパーバイザー プラットフォーム
    • 仮想マシンプラットフォーム
  4. Microsoft Storeを開き、検索窓でUbuntu 18.04 LTSと検索し、入手をクリック。インストールを完了します。
  5. スタートメニューからUbuntuを起動し、ユーザー名とパスワードを設定します。
Installing, this may take a few minutes...
Installation successful!
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: ←ユーザー名を入力
Enter new UNIX password: ←パスワードを入力

WSL2へアップグレード

公式サイトの手順に従いWSL2を導入します。
コンポーネントの有効化はGUIからすでに行ったのでスキップします。

PowerShellを管理者権限で実行し、以下のコマンドを叩きます。

wsl --set-version Ubuntu-18.04 2

バージョン確認を行い、VERSIONが2となっていればWSL2の導入は完了です。

wsl -l -v

Dockerの導入

WLS2で動くようになったLinux環境にDockerを導入していきます。
基本的にはDockerの公式の案内に従います。

Dockerをインストールする

公式サイトで案内されているインストール方法のうち、今回は簡単そうな"Install using the convenience script"を試してみます。
正式なプロダクトへの使用は推奨されていないらしいので自己責任でお願いいたします。

WindowsのスタートメニューからUbuntuを開き、以下のコマンドを叩きます。

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
$ sudo usermod -aG docker your-user

これだけで完了です。
念のためバージョンをチェックします。

$ docker -v

docker-composeをインストールする

[公式サイト]のLinux用の案内に従い、以下のコマンドを叩きます。

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

以上です。
バージョンチェックもしてみます。

$ docker-compose --version

WSL1ではここまで完了したとしても、いざdocker-composeでコンテナを起動しようとするとコケてしまうようでした。
それはWSL1はネットワークまわりが不完全だったかららしいですが、WSL2ではそれが改善されているようです。

おまけ:Laravel環境の導入

せっかくなのでdocker-composeでLaraDockを立ち上げてみます。
こちらはメイントピックから外れるので詳細は記しません。
以下の記事を参考にインストールを行いました。

デーモンとコンテナを起動すると、無事にlocalhostからウェルカムページを見ることができました。
docker-composeもlocalhostもきちんと生きていますね!

$ sudo /etc/init.d/docker start
$ docker-compose up -d workspace nginx mysql

image.png

まとめ

VMを立てることなくWindows 10 HomeのWSL2でLinuxを動かしdocker-composeを使うことができました。

WSL2を使うにはWindows Insider Programへの参加が必要でしたが、今回用いたWSL2以外にもWindows 10 Pro + Hyper-VDocker for WindowsでDockerを動かすことができるようなので、要件に合わせて使い分けたいですね。

参考


明日は@iNakamuraくんが自然言語処理について書いてくれるようです。お楽しみに!

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

アプリケーションのHTTPプロキシのテスト環境を作る

やりたいこと〜

ツール類やデスクトップアプリケーションを作る際に HTTP プロキシ対応をしたりしますが、テストする際に環境がなくて困ったりします。

この記事では docker-compose を使って、テスト用の HTTP プロキシ環境をいつでも再現できるようにします。

UNIX 系 OS ベースの話になります。
NTLM はしません。

コード

本記事のすぐ動くコードは ↓ にあります。

単に HTTP プロキシを立てる

以下のコードを書きます。

  • docker-compose.yml
  • Dockerfile
  • squid.conf.template (envsubst 用のテンプレートファイル)
  • .env (環境変数 = 変更可能なパラメータ)

まず .env ファイルで Squid HTTP Proxy の待受ポートと、BASIC 認証の有無を、環境変数として指定できるようにします。

# 待受ポート
HTTP_PORT=3128

# BASIC 認証の設定 (default ではコメントアウト → "#" で OFF)
BASIC_AUTH_COMMENT_OUT=#
BASIC_AUTH_USERNAME=squid
BASIC_AUTH_PASSWORD=password

オーケストレーションファイル docker-compose.yml でコンテナの起動方法を定義します。

version: '3.1'
services:
  squid:
    build: ./squid
    expose:
      - "${HTTP_PORT}"
    ports:
      - "${HTTP_PORT}:${HTTP_PORT}"
    environment:
      - HTTP_PORT
      - BASIC_AUTH_COMMENT_OUT
      - BASIC_AUTH_USERNAME
      - BASIC_AUTH_PASSWORD

コンテナイメージ定義 DockerfileSquid HTTP Proxy の構築をします。

FROM alpine:3.10

# Set correct environment variables.
ENV HOME /root
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8

# install PKGs.
RUN apk update && \
    apk --no-cache add squid libintl apache2-utils && \
    apk --no-cache add --virtual .gettext gettext && \
    cp /usr/bin/envsubst /usr/local/bin/envsubst && \
    apk del .gettext

# edit squid settings.
COPY squid.conf.template /etc/squid/squid.conf.template

# user.
RUN chown -R squid:squid /etc/squid/ /var/run/
USER squid

# entrypoint.
ENTRYPOINT /bin/sh -c " \
    /bin/touch /etc/squid/passwd && \
    /usr/bin/htpasswd -b /etc/squid/passwd ${BASIC_AUTH_USERNAME} ${BASIC_AUTH_PASSWORD} && \
    /usr/local/bin/envsubst < /etc/squid/squid.conf.template > /etc/squid/squid.conf && \
    /usr/sbin/squid -N -d 1 -f /etc/squid/squid.conf "

最後に squid.conf.template です。 長いので 設定ファイルのリンク だけ。
標準の squid.conf に、以下の設定と envsubst 用の変数を埋め込んでいます。

あとはビルドして起動します。

docker-compose build
docker-compose up -d

待受ポートをホストにフォワードしているので、ローカルアクセスできます。

[面倒] HTTP プロキシを経由しないとインターネットに出れない環境を作る

実際にテストする際に、Default Gateway からインターネットに出れちゃう環境だと、ちゃんとプロキシ経由してるか (漏れてて直接インターネットアクセスしてるのあるかも) 保証できないので、そういう環境を作りたいと思います。

作業用の OS イメージは、比較的ユーザーが多いであろう ubuntu:18.04 でいきます。

先程の docker-compose.yml を修正します。

version: '3.1'
services:
  # ↓ここを足しました。
  app:
    image: ubuntu:18.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    privileged: true

  squid:
    build: ./squid
    expose:
      - "${HTTP_PORT}"
    ports:
      - "${HTTP_PORT}:${HTTP_PORT}"
    environment:
      - HTTP_PORT
      - BASIC_AUTH_COMMENT_OUT
      - BASIC_AUTH_USERNAME
      - BASIC_AUTH_PASSWORD

コンテナ起動しなおします。

docker-compose down
docker-compose up -d

コンテナ app に入ります。

docker exec -it squid-for-http-proxy-testing_app_1 bash

ネットワークをいじる為に必要なコマンドを入れます。 

apt update
apt install -y net-tools iputils-ping curl

アプリケーションを動作させる為に必要な物があれば、このタイミングで一緒にインストールしておきます。

この時点ではインターネットにアクセスできます。

curl google.com

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

ルーティングテーブルを見てみます。
① Default Gateway と ② Docker ネットワーク (他のコンテナ) へのルーティングがあります。

route

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.23.0.1      0.0.0.0         UG    0      0        0 eth0
172.23.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

① Default Gateway のルーティングを消します。 (かなり荒っぽい方法ですが...)

route delete default
route

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.23.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

インターネットにアクセスできなくなりました。

curl google.com

curl: (6) Could not resolve host: google.com

プロキシ設定をします。

export http_proxy=http://squid:3128
export https_proxy=${http_proxy}
  • ホスト名 squid は、Squid Proxy コンテナの IP 172.xx.xx.xx を指してします。
  • 当然ですが、上記の設定は、この bash プロセス上だけで有効です。

HTTP プロキシを経由してインターネットに出れました。

curl google.com

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

あとは docker cp {ローカルファイル} {コンテナ名}:/root/ 等を使って、この環境にアプリケーションを送信し、アプリケーションの動作・テストします。

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

Dockerでnginx-proxyを使ったhttps(http2)リバースプロキシで開発環境を便利にする

Docker で nginx-proxy を使った https(http2) リバースプロキシで開発環境を便利にする

前置き

諸先輩方のお知恵を拝借して、楽に開発環境を構築できる時代になりました。良い時代だなぁ〜

とはいえ、自分のやりたい事にジャストな情報というのは意外とないもので・・・

Mac / Win どちらでもとなると希少価値はグンと跳ね上がります!

そこで、個人的なローカル環境をいい感じに構築してみた内容をご紹介します。

内容

(順不同)を参考に

のローカル環境を Docker で構築してみました。

ポイント

  • 極力楽に
  • 極力最新に
  • 極力環境依存ナシに
  • 極力汎用的(自由度高)に

苦労した点

  • Win (Windows10 WSL1) と Mac では工夫しないと共通化出来ない
    • docker-compose でわざわざ Dockerfile を呼んで、ファイルのマウントから COPY に変更
  • SSLサーバ証明書は Let’s Encrypt が公式にも世の中的にも推されているが、ローカル環境では Too Match
    • mkcert で楽々証明書発行

構成

~/local
|--.gitignore
|--LICENSE
|--README.md
|--nginx-proxy
|  |--Dockerfile
|  |--certs
|  |  |--.gitkeep
|  |  |--plantuml.docker.crt
|  |  |--plantuml.docker.key
|  |  |--quickchart.docker.crt
|  |  |--quickchart.docker.key
|  |--docker-compose.yml
|  |--my_proxy.conf
|--plantuml-server
|  |--Dockerfile
|  |--docker-compose.yml
|  |--docker-entrypoint-custom.sh
|--quickchart
|  |--docker-compose.yml

nginx-proxy

docker-compose.yml
version: '3'

services:
  nginx-proxy:
#    image: jwilder/nginx-proxy:alpine
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
#      - ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro
#      - ./certs:/etc/nginx/certs:ro
    environment:
      - TZ=Asia/Tokyo

networks:
  default:
    external:
      name: nginx-proxy

コメントアウトした箇所を Dockerfile へ移動

Dockerfile
FROM jwilder/nginx-proxy:alpine

COPY ./my_proxy.conf /etc/nginx/conf.d/my_proxy.conf
COPY ./certs /etc/nginx/certs

plantuml-server

docker-compose.yml
version: '3'

services:
  plantuml-server:
#    image: plantuml/plantuml-server:jetty
    build:
      context: .
      dockerfile: Dockerfile
    container_name: plantuml-server
    restart: always
#    volumes:
#      - ./docker-entrypoint-custom.sh:/docker-entrypoint-custom.sh
    entrypoint: /docker-entrypoint-custom.sh
    environment:
      - TZ=Asia/Tokyo
      - VIRTUAL_HOST=www.plantuml.docker
      - VIRTUAL_PORT=8080
      - CERT_NAME=plantuml.docker

networks:
  default:
    external:
      name: nginx-proxy

コメントアウトした箇所を Dockerfile へ移動

Dockerfile
FROM plantuml/plantuml-server:jetty

COPY ./docker-entrypoint-custom.sh /docker-entrypoint-custom.sh

※ 公式の https://www.plantuml.com/plantuml に合わせる対応

docker-entrypoint-custom.sh
#!/bin/sh

mv /var/lib/jetty/webapps/ROOT.war /var/lib/jetty/webapps/plantuml.war
exec /docker-entrypoint.sh "$@"

quickchart

docker-compose.yml
version: '3'

services:
  quickchart:
    image: ianw/quickchart
    container_name: quickchart
    restart: always
    environment:
      - TZ=Asia/Tokyo
      - VIRTUAL_HOST=quickchart.docker
      - VIRTUAL_PORT=3400
      - CERT_NAME=quickchart.docker

networks:
  default:
    external:
      name: nginx-proxy

事前準備

/etc/hosts
+127.0.0.1    www.plantuml.docker
+127.0.0.1    quickchart.docker
NoProxy
+, *.docker
  • mkcert (例: plantuml)
sh
cd certs
mkcert "*.plantuml.docker" plantuml.docker
mv _wildcard.plantuml.docker+1-key.pem plantuml.docker.key
mv _wildcard.plantuml.docker+1.pem plantuml.docker.crt
  • 初回(無い場合)
sh
docker network create --driver bridge nginx-proxy

ビルド(nginx-proxy, plantuml-server)

sh
docker-compose build

起動

sh
docker-compose up -d

停止

sh
docker-compose down -v --remove-orphans

その他利用ツール類(参考)

終わりに

リバースプロキシとそれ以外を分離しているので、network さえ繋げてしまえば他の docker 達も environment 設定だけで勝手に振り分けてくれます!素晴らしい!

sh
docker network connect nginx-proxy {コンテナ名}

モチロン上記の例の様に yaml ファイルで同名のネットワークを定義してもOK!

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

docker build を使わずコンテナイメージの中身をいぢる

ことの発端

docker image ls で表示される IMAGE ID ってなんだろう?

$ docker image ls                        # ↓ これ
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
prom/prometheus      latest              7317640d555e        3 weeks ago         130MB
alpine               latest              965ea09ff2eb        6 weeks ago         5.55MB

こちらの記事で「Copy On Write(COW)ファイルシステム」という要素技術を知りました。

コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう - エンジニアHub|若手Webエンジニアのキャリアを考える!

Dockerコンテナを作成する際に、そのベースファイルシステムとなるDockerイメージはOverlayFSのようにレイヤを重ね合わせた形で提供されています。ベースとなるOSレイヤ、そこにアプリケーションに必要なツールやライブラリをインストールしたレイヤ、さらにアプリケーションをインストールしたレイヤなど、複数のレイヤを重ね合わせてDockerイメージとなります。

これを知ると IMAGE ID について憶測ができます。

  • IMAGE ID はレイヤごとのファイル差分の digest 値に関係するのでは?
  • しかし単一レイヤの digest 値では Dockerfile の FROM が何であるかにかかわらず同じになってしまう。全レイヤのファイルか digest 値を合算した後さらに digest した値では?
  • もしも IMAGE ID を偽った不正コンテナイメージが流通できるとしたら問題がある。Docker には IMAGE ID の検証機能と不正なコンテナイメージを拒絶する仕様があるのでは?

わたし、気になります!

調べてみよう・やってみよう

  • docker の IMAGE ID 計算方法を調べる
  • Linux コマンドで手作業でコンテナイメージをビルドする
  • IMAGE ID そのままで中身を改ざんすると docker はどのような挙動をするか調べる

今回はこのあたりまでやってみたいと思います。

検証環境

  • ハードウェア: Pixelbook (Chromebook)
  • OS: Debian GNU/Linux 9 (stretch) Linux on Chromebook
  • Docker CE: 19.03.5
  • Storage Driver: btrfs

まだちょっとマイナーな Linux on Chromebook です。

参考資料

docker CE のコードを全部読んで把握できればすべて理解できるでしょうが、まったく取っ掛かりがないので docker build とか docker image id などで検索してみます。すると、とても参考になる Qiita の記事に出会いました。

Dockerのコードを読む -イメージ生成編-

イメージの構造がわからない状態で読み始めると辛くなるので
先にOCI image-specを読むことをオススメします。
なぜOCIかというと、Dockerのドキュメントより仕様としてしっかりまとまっているのと、
Dockerを参考にしているのもあって大枠は一致しているからです。

なるほど!

仕様を読む前にとりあえず不正なコンテナを食わせてみる

この手のイメージファイルは tar で固めてあるのが定番なので、

  1. docker save でコンテナイメージをファイルとして取得してから展開する
  2. ファイルシステムをいぢる
  3. どこかにあるだろうマニフェストの類はいぢらない
  4. tar で固める。これで不正なコンテナイメージの一丁上がり (たぶん)
  5. docker load で食わせる

という操作をすれば docker が不正なイメージを拒絶するかどうかはわかるでしょう。 (たぶん)

かんたんな元イメージを build

hogehoge と書かれたファイル hogehoge を alpine の /tmp にコピーしただけのイメージを作ります。

Dockerfile
FROM alpine:latest
COPY hogehoge /tmp/ # hogehoge は echo 'hogehoge' >| hogehoge で予め作っておきます
$ docker build . -t hogehoge:latest
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM alpine:latest
 ---> 965ea09ff2eb
Step 2/2 : COPY hogehoge /tmp/
 ---> 4936a59b94ad
Successfully built 4936a59b94ad
Successfully tagged hogehoge:latest

$ docker image ls
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
hogehoge             latest              4936a59b94ad        27 seconds ago      5.55MB

IMAGE ID は 4936a59b94ad ですね。
念の為コンテナを走らせて、/tmp/hogehoge の内容を確認しておきます。

$ docker run -it hogehoge:latest /bin/sh
/ # cat /tmp/hogehoge
hogehoge

イメージをファイルに save して展開

$ docker save hogehoge:latest > hogehoge.tgz
ls -lh
合計 5.6M
-rw-r--r-- 1 weakboson weakboson   39 12月  7 21:44 Dockerfile
-rw-r--r-- 1 weakboson weakboson    9 12月  8 18:33 hogehoge
-rw-r--r-- 1 weakboson weakboson 5.6M 12月  8 20:37 hogehoge.tgz

$ mkdir image
$ tar xf hogehoge.tgz -C image # 後でわかりますが tar + gzip ではなく tar でした

$ cd image
$ ls -lh
合計 12K
drwxr-xr-x 1 weakboson weakboson   40 12月  8 20:32 0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0
drwxr-xr-x 1 weakboson weakboson   40 12月  8 20:32 2ce302b70292e3484f04aa768fde2c54a93c208b28e205ffda9e373ffd40a575
-rw-r--r-- 1 weakboson weakboson 1.7K 12月  8 20:32 4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json
-rw-r--r-- 1 weakboson weakboson  281  1月  1  1970 manifest.json
-rw-r--r-- 1 weakboson weakboson   91  1月  1  1970 repositories

お。さっそく manifest.json ともう一つ IMAGE ID の由来になってそうな 4936a59b94ad..(中略).json というファイルがあります。このようなファイルがあれば sha256 digest をとりたくなるのが人の性 (サガ)

$ sha256sum 4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json
4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9  4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json

ファイル名と sha256 digest が一致しますね。

2 つの json はなんだろう?

manifest.json
[
  {
    "Config": "4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json",
    "RepoTags": [
      "hogehoge:latest"
    ],
    "Layers": [
      "2ce302b70292e3484f04aa768fde2c54a93c208b28e205ffda9e373ffd40a575/layer.tar",
      "0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0/layer.tar"
    ]
  }
]

manifest.json にはもう一方の json ファイル名とサブディレクトリにある tar ファイル、そしてコンテナイメージ名とラベルがあります。もう一方の json ファイルが Config というキーにあるので、仮に Config.json と表記します。

Config.json
{
  "architecture": "amd64",
  "config": {
    "Hostname": "",
    // 中略
    "Image": "sha256:965ea09ff2ebd2b9eeec88cd822ce156f6674c7e99be082c7efac3c62f3ff652",
    // 中略
  },
  "container_config": {
    // 中略
    "Cmd": [
      "/bin/sh",
      "-c",
      "#(nop) COPY file:ee9321d69eb59158edcb22888b3c6665be5c9230a5713a2bfeb0f069358e86f8 in /tmp/ "
    ],
    // 中略
    "Image": "sha256:965ea09ff2ebd2b9eeec88cd822ce156f6674c7e99be082c7efac3c62f3ff652",
    // 中略
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:77cae8ab23bf486355d1b3191259705374f4a11d483b24964d2f729dd8c076a0",
      "sha256:dbdb51e45bed7ab135ee0050a34ce75f64dee736c78c936e20bbe0f7a02d6b33"
    ]
  }
}

Config.json には自身の sha256 digest が何回かと、 diff_ids という sha256 digest 値が2種登場します。

……直感ですがサブディレクトリにある tar ファイルが怪しいと思います。
サイズと sha256 digest 値を見てみましょう。

$ ls -lh */layer.tar
-rw-r--r-- 1 weakboson weakboson 2.5K 12月  8 20:32 0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0/layer.tar
-rw-r--r-- 1 weakboson weakboson 5.6M 12月  8 20:32 2ce302b70292e3484f04aa768fde2c54a93c208b28e205ffda9e373ffd40a575/layer.tar

$ sha256sum */layer.tar
dbdb51e45bed7ab135ee0050a34ce75f64dee736c78c936e20bbe0f7a02d6b33  0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0/layer.tar
77cae8ab23bf486355d1b3191259705374f4a11d483b24964d2f729dd8c076a0  2ce302b70292e3484f04aa768fde2c54a93c208b28e205ffda9e373ffd40a575/layer.tar

お。2ファイルの digest 値が diff_ids それぞれと一致しました。

ところで layer.tar ってもう見るからにレイヤのファイルシステムアーカイブっぽいですよね。サイズが大きな 2ce302b70292(中略)/layer.tar が FROM である alpine:latest だとすると、小さい 0d27994a6e9f(中略)/layer.tar を展開したら /tmp/hogehoge が出てきそうだと思いませんか?

layer.tar を展開する

$ ls -lh
合計 12K
-rw-r--r-- 1 weakboson weakboson    3 12月  8 20:32 VERSION
-rw-r--r-- 1 weakboson weakboson 1.3K 12月  8 20:32 json
-rw-r--r-- 1 weakboson weakboson 2.5K 12月  8 20:32 layer.tar

$ mkdir layer
$ tar xf layer.tar -C layer

$ find layer
layer
layer/tmp
layer/tmp/hogehoge

$ cat layer/tmp/hogehoge
hogehoge

あたりです!

ところで VERSION と json も見てみましょう。

VERSION
1.0
json
{
  "id": "0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0",
  "parent": "2ce302b70292e3484f04aa768fde2c54a93c208b28e205ffda9e373ffd40a575",
  "created": "2019-12-08T11:32:35.014345961Z",
  // 中略
    "Cmd": [
      "/bin/sh",
      "-c",
      "#(nop) COPY file:ee9321d69eb59158edcb22888b3c6665be5c9230a5713a2bfeb0f069358e86f8 in /tmp/ "
    ],
  // 後略

id は layer.tar のあるサブディレクトリ名で parent はもう一方、alpine だと踏んでいる方のディレクトリ名ですね。それに Dockerfile に記述したコマンドらしきものも見えます。

悪いことしましョ

内容を改ざんして json 類はそのままでパックし直します。

まず展開した layer 中の /tmp/hogehoge の内容を fugafuga に書き換えます。

$ cd layer
$ echo 'fugafuga' >| tmp/hogehoge
$ cat tmp/hogehoge
fugafuga

展開用に自作したサブディレクトリをお掃除しつつ tgz にパックします。 layer.tar は名前からして gzip かけてないようですから無圧縮にします。

$ tar cf ../layer.tar ./*
$ cd ../
$ rm -rf layer

$ cd ../
$ tar czf ../fugafuga.tgz ./*
$ cd ../
$ ls -lh *.tgz

-rw-r--r-- 1 weakboson weakboson 2.6M 12月  8 21:24 fugafuga.tgz
-rw-r--r-- 1 weakboson weakboson 5.6M 12月  8 20:37 hogehoge.tgz

あれ?だいぶサイズが違いますね……まあいいか。(イメージは正しくは gzip しない tar でした。)

そしておもむろに docker に食わせたいのですが、各種 json ファイル中の sha256 digest が変わっていないせいでしょうか?すぐに食べさせても何も起こりません。
一度 docker が持っているイメージを削除します。先程確認のために起動したコンテナも先に消します。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
5251c56e7b95        4936a59b94ad        "/bin/sh"           55 minutes ago      Exited (0) 54 minutes ago                       infallible_rubin
$ docker container rm 5251c56e7b95
5251c56e7b95

$ docker image rm hogehoge:latest
Untagged: hogehoge:latest
Deleted: sha256:4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9

食らえ!(食べないでー)

さあ、改めて召し上がれー♪

$ docker load -i fugafuga.tgz
dbdb51e45bed: Loading layer [==================================================>]  10.24kB/10.24kB
invalid diffID for layer 1: expected "sha256:dbdb51e45bed7ab135ee0050a34ce75f64dee736c78c936e20bbe0f7a02d6b33", got "sha256:4a64864cd5b3e1ac07d0b47eaf126746ba59e95897094527788502ebca556db2"

はい、ペッと吐き出してくれました!偉いぞ docker!

docker load は内容が改ざんされたコンテナイメージを受け付けない!

人力 docker build 前に OCI の仕様にあたってみよう

OCI Image Format リポジトリを "image id" や先ほど docker が改ざんイメージをペッと吐き出したエラーに表示された "diffID" で検索すると OCI Image Configuration という Markdown で書かれた仕様が見つかります。

この仕様を読むと Config.json と表記していたファイルは OCI では Image JSON とか configuration json と呼ぶようで、たしかにこのファイルの sha256 digest が Image ID になるようです。

ImageID

Each image's ID is given by the SHA256 hash of its configuration JSON.
It is represented as a hexadecimal encoding of 256 bits, e.g., sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9.
Since the configuration JSON that gets hashed references hashes of each layer in the image, this formulation of the ImageID makes images content-addressable.↲

まとめると以下のようなことが書かれていると理解しました。

  • Docker の IMAGE ID は configuration json (Image JSON) の sha256 digest 値
  • Configuration json は各レイヤーの Layer DiffID を持つ
  • Layer DiffID はレイヤーごとの非圧縮 tar アーカイブの sha256 digest 値

Let's 人力 docker build

Digest 値を正しく修正する

  1. "/tmp/hogehoge" の内容を修正した後の tar から Layer DiffID を計算
  2. configuration json 中の diff_id を計算値に置換
  3. configuration json の digest 値を計算
  4. configuration json を新しい digest 値でリネーム
  5. manifest.json 中の configuration json ファイル名を計算値に置換

4, 5 の操作は OCI の image-spec.mdmanifest.md には記述されていないように読めましたが、docker イメージ中の manifest.json には対応があるので念のため修正しました。 (修正しないでも動作するかは未検証です。)

# 0. 置換用に前の Layer DiffID を退避
$ OLD_DIGEST=dbdb51e45bed7ab135ee0050a34ce75f64dee736c78c936e20bbe0f7a02d6b33

# 1. "/tmp/hogehoge" の内容を修正した後の tar から Layer DiffID を計算
$ NEW_DIGEST=`sha256sum 0d27994a6e9f841bf6a9973941008b303a7234d70c640570d70148b5b5d2a3d0/layer.tar | awk '{print $1}'`
$ echo $NEW_DIGEST
4a64864cd5b3e1ac07d0b47eaf126746ba59e95897094527788502ebca556db2

# 2. configuration json 中の diff_id を計算値に置換
$ sed -i "s/${OLD_DIGEST}/${NEW_DIGEST}/g" 4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json

$ OLD_IMAGE_ID=4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9

# 3. configuration json の digest 値を計算
$ NEW_IMAGE_ID=`sha256sum 4936a59b94ad4d2c2a4bce855e7891276f4c2b3fb3b25dbe3b2774ae4185d6e9.json| awk '{print $1}'`
$ echo $NEW_IMAGE_ID
53667683a1bc3a9de27dbb96457ba102d4cd9dc6a78cba9a534b3d626fe2ff63

# 4. configuration json を新しい digest 値でリネーム
$ mv ${OLD_IMAGE_ID}.json ${NEW_IMAGE_ID}.json

# 5. manifest.json 中の configuration json ファイル名を計算値に置換
$ sed -i "s/${OLD_IMAGE_ID}/${NEW_IMAGE_ID}/g" manifest.json

Digest 値が正統な Image tgz を作成して docker に食わせる

$ tar czf ../fugafuga.tgz ./*
$ cd ../
$ docker load -i fugafuga.tgz
4a64864cd5b3: Loading layer [==================================================>]  10.24kB/10.24kB
Loaded image: hogehoge:latest

お!食べてくれましたね……

$ docker image ls
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
hogehoge             latest              53667683a1bc        4 hours ago         5.55MB

他のメタ情報を修正していないので CREATED は hogehoge を docker build した時刻になっているようです。中身はきちんと hogehoge -> fugafuga が反映されているでしょうか?起動できるでしょうか?

$ docker run -it hogehoge:latest /bin/sh
/ # cat /tmp/hogehoge
fugafuga

いけました!内容を変更後も IMAGE ID を正しく計算・設定すると docker で扱えます!

まとめ

  • docker の IMAGE ID は configuration json (Image JSON) の sha256 digest 値です。configuration json はさらに各レイヤの Layer DiffID という sha256 digest 値を持ちます
  • レイヤを改ざんして IMAGE ID 計算時と内容が変わったイメージは docker load できません
  • レイヤを変更しても正しく IMAGE ID を計算・設定したイメージは docker load, run できます (docker build 自身がやってることの最低限の操作に相当するのでしょう)

続けて調べたいこと

イメージサイズが大きなサイドカーコンテナを過剰に忌避するものでもないのか検証する

同じホストで同じ構成のコンテナを複数作成する場合、各コンテナ同士では多くの共有可能なバイナリやライブラリがあります。コンテナごとにファイルシステムをまるごと用意する、とはディスクスペースを無駄に消費していることと同義なのです。また、大きな環境の場合、ファイルシステムの作成にも時間がかかりコンテナの起動が遅くなってしまう可能性もあります。こうした無駄を改善するのが、COW(Copy On Write)ファイルシステムです。

コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう - エンジニアHub|若手Webエンジニアのキャリアを考える! から引用

最近 Kubernetes でアプリ運用をするにあたり Envoy や mtail のようなサイドカーコンテナのイメージサイズはできるだけ小さくしたいと考えていました。しかし今回の記事を書くために上記引用記事を再読して COW ファイルシステムの性質を再認識しました。
既存レイヤのファイルを変更しなければ1ワーカーノードにサイドカーコンテナがいくつあろうと1個分のディスクしか消費しないはずで、そうそうイメージのサイズにセンシティブにならなくてよいのかもしれません。意図的にイメージサイズが大きなコンテナを複数起動して検証してみようと思います。

Docker CE の実装を読んでいぢってみる

今回 OCI の仕様書と試行錯誤で動作できてしまったので Docker のコードをまーーーったく読みませんでした。Docker のコードを読んで改修してみて、例えば Image ID 詐称イメージでも食う docker とかビルドしてみたいものです。今回は docker load でブロックされることを確認しましたが、おそらく docker のほとんどあらゆる機能にコンテナイメージの検証プロセスが含まれているのではと予想しています。

エラーメッセージで検索した感じ docker load をブロックしたコードはこのあたりかな?
https://github.com/docker/docker-ce/blob/d49c4ca453ce4d1161ce5fb2482be7538bf73e07/components/engine/image/tarexport/load.go#L119-L121

明日は @hazanyaan の「Web脆弱性を体感してみよう」です。

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

Dockerを使用してNuxt.jsアプリを作成して実行する

Use Docker to Create and Run a Nuxt.js App

このチュートリアルではUbuntu 19.04を使用しています。

I`m using Ubuntu 19.04 for this tutorial.

一時コンテナを実行してnuxt.jsアプリを作成します

run temporary container to create nuxt.js app

sudo docker container run -it -v $(pwd):/app -w /app node:12.13.1-alpine /bin/sh

コンテナ内に、「nuxt」と「nuxt-create-app」をインストールします

Inside container, install nuxt and nuxt-create-app

npm install --save nuxt
npm install -g create-nuxt-app

完了したら、Nuxtアプリを作成できます

Once done, you can create the Nuxt app

npx create-nuxt-app <project-name>

それが完了したら、コンテナを「exit」し、プロジェクトフォルダの所有者を変更できます

Once that is done, you can exitthe container, change the owner of the project folder

sudo chown -R myuser:myuser <project-name>

プロジェクトフォルダーに「cd」し、「Dockerfile」と「docker-compose.yml」の両方を作成します

cd into the project folder and create both the Dockerfile and docker-compose.yml

Dockerfile
FROM node:12.13.1-alpine 

WORKDIR /app

RUN apk update && \
    apk add git && \
    npm install -g npm && \
    npm install -g @vue/cli \ 
    npm install --save nuxt

ENV HOST 0.0.0.0
EXPOSE 3000

問題がないように、Dockerコンテナの同じバージョンを最初から使用することを忘れないでください。

Remember to use the same version from the docker container from the beginning, as to not have issues.

docker-compose.yml
version: '3'

services:
  nuxt:
    build: .
    tty: true
    command: npm run dev
    volumes:
      - .:/app
    ports:
      - "3000:3000"

これにより、docker-composeを実行してプロジェクトをビルドおよび実行できます

With this, you can run docker-compose to build and run the project

sudo docker-compose up --build

コンテナーをダウンロードしてイメージをビルドするには、最初にしばらく時間がかかります。一度完了すると、プロジェクトのホームページがURL「localhost:3000」に表示されます

This will take a while the first time, in order to download the container and build the image, once done, you can see the project home page on the url localhost:3000

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

dockerでgpuを使用するためのnvidia-docker2インストールガイド

概要

  • dockerでGPUを用いるためにはnvidia-docker2のインストールが必要.
  • nvidia-docker2のlinuxへのインストール方法が少々面倒だったので,自分用にメモ.

nvidia-docker2のインストール方法

installガイド
# リポジトリの登録
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update

# nvidia-docker2のインストール
sudo apt-get install -y nvidia-docker2
sudo pkill -SIGHUP dockerd

# 動作テスト
docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi

これで,dockerを起動する際にruntimeオプションを追加し,--runtime=nvidiaと記述すればdocker内でGPUが認識されます.

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

Mojave で Docker Volume のバックアップ(Docker v19.03)

docker volume create で作成したボリュームをローカルにバックアップしたい。

しかし、Mac には docker volume inspect で表示される Mountpoint にボリュームがない。

また、Docker Engine のバージョンによって挙動が違うらしく、mac docker volume バックアップ」で Qiita 記事をググっても記事の内容がバラバラで困った。簡単にバックアップ&復元できないか

TS;DR

Mac の Docker Desktop の場合、Alpine Linux などの軽量コンテナに下記2つを一旦マウントし tar などでアーカイブするのがらくです。(バックアップしたいボリューム名が data-hoge の場合)

  1. バックアップしたいボリューム(-v data-hoge:/data
  2. バックアップ先のディレクトリ(-v $(pwd)/backup:/backup
docker run --rm -v data-qithub:/data -v $(pwd)/backup:/backup alpine tar cvf /backup/backup.tar /data

復元は上記の逆の手順で、アーカイブとボリュームをマウントして、ボリューム内に解凍します。

TL;DR

ボリュームの確認
$ docker volume ls
DRIVER              VOLUME NAME
local               data-hoge
バックアップ
$ # バックアップ・ディレクトリの作成&移動
$ cd mkdir ~/my_backup && cd $_
$ # バックアップ開始
$ docker run --rm \
    -v data-hoge:/data \
    -v $(pwd)/backup:/backup \
    alpine \
    tar cvf /backup/backup.tar /data
tar: removing leading '/' from member names
data/
data/hello.txt
...

$ # 作成されたバックアップの確認
$ ls
backup
$ # ディレクトリ構造
$ tree
.
└── backup
    └── backup.tar

1 directory, 1 file
アーカイブの解凍
$ tar -xvf ./backup/backup.tar
x data/
x data/hello.txt
...
$ # 解凍ファイルの確認
$ ls
backup  data
$ # ディレクトリ構造
.
├── backup
│   └── backup.tar
└── data
    ├── ...
    └── hello.txt

Mac の Docker ボリュームは特殊

docker volume create --name data-hoge とボリュームを作成した場合、inspect コマンドでボリューム情報を表示すると以下のようになります。

ボリューム情報の表示
$ docker volume inspect data-hoge
[
    {
        "CreatedAt": "2019-12-09T04:28:16Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
        "Name": "data-hoge",
        "Options": {},
        "Scope": "local"
    }
]

しかし、上記の Mountpoint のパスにデータがあるかと思いきや、ありません。

Mountpointをlsしてみる
$ ls /var/lib/docker/volumes/data-hoge/_data
ls: /var/lib/docker/volumes/data-hoge/_data: No such file or directory

$ # そもそも /var/lib/docker がない
$ ls /var/lib/
postfix

これは Mac の場合は Docker は VM(仮想マシン) 上で動いているためです。つまり docker volumes create で作成されたボリュームも仮想マシンのイメージの中にされるということなので、ローカルには直接作成されません。

Mac の場合、仮想マシンのイメージは以下のディレクトリになります。(Docker v19.03.5 現在)

Dockerの仮想マシン・イメージの保存先
~/Library/Containers/com.docker.docker/Data/vms/0/

問題は、このディレクトリから該当するイメージと、そこからデータを引き出す方法がわかりづらいこと。

どのファイルを選べばいいのかわからない
$ tree ~/Library/Containers/com.docker.docker/Data/vms/0/
/Users/admin/Library/Containers/com.docker.docker/Data/vms/0/
├── 00000002.000005f4
├── 00000002.00001000
├── 00000002.00001001
├── 00000002.00001002
├── 00000002.0000f3a4
├── 00000002.0000f3a5
├── 00000003.000005f5
├── 00000003.00000948
├── Docker.raw
├── config.iso
├── connect
├── data
├── guest.000005f5 -> 00000003.000005f5
├── guest.00000948 -> 00000003.00000948
├── hyperkit.json
├── hyperkit.pid
├── lifecycle-server.sock
├── log
├── nic1.uuid
└── tty -> /dev/ttys000

2 directories, 18 files

screentty で仮想マシンに接続する方法もあるようなのですが、確認ができるというだけでローカルにデータを保存するには煩雑な作業が必要そうです。

$ # 仮想マシンの tty に接続
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
cd
docker-desktop:~# # 仮想マシン上で Mountpoint を確認
docker-desktop:~# ls /var/lib/docker/volumes/data-qithub/_data
hello.txt ...

docker-desktop:~# # Ctrl+a -> Crtl+k で exit
Really kill this window [y/n] y
[screen is terminating]

また、これらのファイルを下手に触ってイメージを壊したりするのも危険なので、確実で安全なバックアップ方法がないか探したところ、公式に書いてありました。ダミーのコンテナに、ボリュームとローカルのディレクトリをマウントして tar アーカイブする方法です。

For example, create a new container named dbstore:

$ docker run -v /dbdata --name dbstore ubuntu /bin/bash

Then in the next command, we:

  • Launch a new container and mount the volume from the dbstore container
  • Mount a local host directory as /backup
  • Pass a command that tars the contents of the dbdata volume to a backup.tar file inside our /backup directory.
$ docker run --rm \
  --volumes-from dbstore \
  -v $(pwd):/backup \
  ubuntu tar cvf /backup/backup.tar /dbdata

Backup, restore, or migrate data volumes @ docs.docker.com より)

確かにシンプルで確実な方法でした。なにより直感的です。

ちなみに、リカバリーは逆の手順で、ボリューム内に tar ファイルを解凍します。

リカバリー方法
$ # リカバリー用の空のボリューム作成 (dbstore2)
$ docker volume create --name dbstore2

$ # リカバリー
$ docker run --rm \
    -v dbstore2:/dbdata \
    -v $(pwd)/backup.tar:/backup/backup.tar \
    ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

参考文献

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

Mojave で Docker Volume のバックアップ(Docker v19.03, macOS 10.14)

docker volume create で作成したボリュームをローカルにバックアップしたい。

しかし、Mac には docker volume inspect で表示される Mountpoint にボリュームがない。

また、Docker Engine のバージョンによって挙動が違うらしく、mac docker volume バックアップ」で Qiita 記事をググっても記事の内容がバラバラで困った。簡単にバックアップ&復元できないか

TS;DR

Mac の Docker Desktop の場合、Alpine Linux などの軽量コンテナに下記2つを一旦マウントし tar などでアーカイブするのがらくです。(以下は、バックアップしたいボリューム名が data-hoge の場合)

  1. バックアップしたいボリューム(-v data-hoge:/data
  2. バックアップ先のディレクトリ(-v $(pwd)/backup:/backup
docker run --rm -v data-hoge:/data -v $(pwd)/backup:/backup alpine tar cvf /backup/backup.tar /data

復元は上記の逆の手順で、アーカイブとボリュームをマウントして、ボリューム内に解凍します。

TL;DR

ボリュームの確認
$ docker volume ls
DRIVER              VOLUME NAME
local               data-hoge
バックアップ
$ # バックアップ・ディレクトリの作成&移動
$ cd mkdir ~/my_backup && cd $_
$ # バックアップ開始
$ docker run --rm \
    -v data-hoge:/data \
    -v $(pwd)/backup:/backup \
    alpine \
    tar cvf /backup/backup.tar /data
tar: removing leading '/' from member names
data/
data/hello.txt
...
アーカイブの解凍と確認
$ cd ~/my_backup
$ # 作成されたバックアップの確認
$ ls
backup
$ # ディレクトリ構造の確認
$ tree
.
└── backup
    └── backup.tar

1 directory, 1 file
$
$ # アーカイブの解凍
$ tar -xvf ./backup/backup.tar
x data/
x data/hello.txt
...
$ # 解凍ファイルの確認
$ ls
backup  data
$ # ディレクトリ構造の確認
$ tree
.
├── backup
│   └── backup.tar
└── data
    ├── ...
    └── hello.txt

Mac の Docker ボリュームは特殊

docker volume create --name data-hoge とボリュームを作成した場合、inspect コマンドでボリューム情報を表示すると以下のようになります。

ボリューム情報の表示
$ docker volume inspect data-hoge
[
    {
        "CreatedAt": "2019-12-09T04:28:16Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
        "Name": "data-hoge",
        "Options": {},
        "Scope": "local"
    }
]

しかし、上記の Mountpoint のパスにデータがあるかと思いきや、ありません。

Mountpointをlsしてみる
$ ls /var/lib/docker/volumes/data-hoge/_data
ls: /var/lib/docker/volumes/data-hoge/_data: No such file or directory

$ # そもそも /var/lib/docker がない
$ ls /var/lib/
postfix

これは Mac の場合は Docker は VM(仮想マシン) 上で動いているためです。つまり docker volumes create で作成されたボリュームも仮想マシンのイメージの中に作成されるということなので、ローカルには直接作成されません。

Mac の場合、仮想マシンのイメージは以下のディレクトリになります。(Docker v19.03.5 現在)

Dockerの仮想マシン・イメージの保存先
~/Library/Containers/com.docker.docker/Data/vms/0/

問題は、このディレクトリから該当するイメージと、そこからデータを引き出す方法がわかりづらいこと。

どのファイルを選べばいいのかわからない(treeコマンドはbrewで別途インストール済み)
$ tree ~/Library/Containers/com.docker.docker/Data/vms/0/
/Users/admin/Library/Containers/com.docker.docker/Data/vms/0/
├── 00000002.000005f4
├── 00000002.00001000
├── 00000002.00001001
├── 00000002.00001002
├── 00000002.0000f3a4
├── 00000002.0000f3a5
├── 00000003.000005f5
├── 00000003.00000948
├── Docker.raw
├── config.iso
├── connect
├── data
├── guest.000005f5 -> 00000003.000005f5
├── guest.00000948 -> 00000003.00000948
├── hyperkit.json
├── hyperkit.pid
├── lifecycle-server.sock
├── log
├── nic1.uuid
└── tty -> /dev/ttys000

2 directories, 18 files

screentty で仮想マシンに接続する方法もあるようなのですが、確認ができるというだけでローカルにデータを保存するには煩雑な作業が必要そうです。

$ # 仮想マシンの tty に接続
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
cd
docker-desktop:~# # 仮想マシン上で Mountpoint を確認
docker-desktop:~# ls /var/lib/docker/volumes/data-qithub/_data
hello.txt ...

docker-desktop:~# # Ctrl+a -> Crtl+k で exit
Really kill this window [y/n] y
[screen is terminating]

また、これらのファイルを下手に触ってイメージを壊したりするのも危険なので、確実で安全なバックアップ方法がないか探したところ、公式に書いてありました。ダミーのコンテナに、ボリュームとローカルのディレクトリをマウントして tar アーカイブする方法です。

For example, create a new container named dbstore:

$ docker run -v /dbdata --name dbstore ubuntu /bin/bash

Then in the next command, we:

  • Launch a new container and mount the volume from the dbstore container
  • Mount a local host directory as /backup
  • Pass a command that tars the contents of the dbdata volume to a backup.tar file inside our /backup directory.
$ docker run --rm \
    --volumes-from dbstore \
    -v $(pwd):/backup \
    ubuntu tar cvf /backup/backup.tar /dbdata

Backup, restore, or migrate data volumes @ docs.docker.com より)

確かにシンプルで確実な方法でした。なにより直感的です。

ただ、ubuntu コンテナを使うよりは alpine の軽量コンテナを使った方が個人的に好みです。また、上記の方法だと処理後に不要になったダミー・コンテナが残ってしまいます。ボリュームが生きている(docker volume ls で表示される)のであれば、以下のようにバックアップするのが簡単だと思います。

ボリューム名がdbstoreの場合
docker run --rm \
  -v dbstore:/dbstore \
  -v $(pwd)/backup:/backup \
  alpine tar cvf /backup/backup.tar /dbstore

ちなみに、リカバリーは逆の手順で、ボリューム内に tar ファイルを解凍します。

リカバリー方法
$ # リカバリー用の空のボリューム作成 (dbstore2)
$ docker volume create --name dbstore2

$ # リカバリー
$ docker run --rm \
    -v dbstore2:/dbdata \
    -v $(pwd)/backup.tar:/backup/backup.tar \
    ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

参考文献

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

Mattermostに仮想サーバのディスク空き容量お知らせBotを作る

富士通システムズウェブテクノロジー Advent Calendar 2019 11日目の投稿です。
(お約束)記事の内容は全て個人の見解であり、執筆内容は執筆者自身の責任です。所属する組織は関係ありません。
また、執筆者はSE一年目の新人のため、至らない点があればご指摘いただけると嬉しいです。

初めに

仮想サーバのディスク容量って気が付いたらいっぱいになっていませんか?コマンドがTabキーで補完されなくなってから気づくと対応が面倒ですよね。
そこで、仮想サーバのディスク容量を指定した日時にチャットツールに通知するBotを作成しました。

  • 前提条件

    • 仮想サーバはエクストラネット環境
    • ローカルはWindows10
    • 使用OSSはDocker,Node-RED
    • Mattermostのチーム管理者かシステム管理者になる

環境構築

まずは環境構築です。Docker上にNode-REDをインストールして、Node.jsを使えるように設定します。

手順

以下のURLからWindows10にDockers for Windowsをインストールします。
https://docs.docker.com/docker-for-windows/install/

docker-compose設定ファイルのdocker-compose.ymlを作成します。

docker-compose.ymlの例

version: '2'
services:
  nodered:
    ports:
      - 8088:1880
    volumes:
      - C:/xxx/Node-RED/nodered-data-pj:/data
    environment:
      - TZ=Asia/Tokyo
      - NODE_RED_ENABLE_PROJECTS=true
    image:
      nodered/node-red:latest
    restart: unless-stopped

Windows PowerShellを管理者として実行します。

docker-compose.ymlのあるフォルダ上でdocker-compose up -dを実行し、バックグラウンドでコンテナを立ち上げます。

docker exec -it node-red_nodered_1 bashコマンドを実行し、コンテナ内に入ります。docker_command1.JPG

コンテナ内でnpm install --save node-sshコマンドを実行して、node-sshをインストールします。
docker_command2.JPG

Node-REDの追加モジュールを利用するために、settings.jsを編集します。
settings.jsの編集例

   functionGlobalContext: {
   sensor:require('node-ssh')
   // os:require('os'),
   // jfive:require("johnny-five"),
   // j5board:require("johnny-five").Board({repl:false})
   },

exitコマンドを実行し、コンテナから出ます。

docker-compose restartコマンドでアプリケーションを再起動します。

Mattermostにディスク容量を通知するBot作成

Bot作成をします。前提としてチーム管理者かシステム管理者でないとウェブフックを追加できないため、権限のある人に依頼しましょう。

事前準備

Mattermostのメインメニューから統合機能を選択します。
統合機能のメニューから内向きのウェブフックを追加します。
webhook.JPG
ウェブフックを編集します。タイトルと説明を入力してください。
 ※チャンネルはペイロードで制御するため入力不要です。

手順

環境構築のdocker-compose.ymlで指定したポートを入力して、Node-REDに入ります。
 ※Node-REDの基本的な使い方に関してはこちらの記事を参考にしました。
  Node-RED超入門

Node-REDの画面例
Node-RED.main.JPG

injectノードでBotを流す日時の設定をします。
Node-RED.inject.JPG
functionノードに、仮想サーバ内にSSH接続してディスク容量確認コマンドを実行するコードを書きます。

node.js
async function main() {

    const node_ssh = global.get("sensor");
    const ssh = new node_ssh();

    const sshPassword = '********';

    // 接続
    await ssh.connect({
        host: 'XXX.XX.XX.XX',
        port: 22,
        username: '****',
        password: sshPassword
    });

    // コマンド実行
    msg.ssh = await ssh.execCommand('df -H', {options: {pty: true}});

    // 切断
    ssh.dispose();
    node.send(msg);
}

main();

functionノードに、MattermostにBotとしてメッセージを流すコードを書きます。

node.js
msg.payload = {};
msg.payload.icon_url = 
"https://xxxxxxxx/xxxxxxxxxxxx";
msg.payload.username = "ディスク空き容量お知らせ";
msg.payload.text = "@xxx.xxx\n```\n"+msg.ssh.stdout+"\n```";
msg.payload.channel = "xxxx"
msg.url = "https://mattermost.xxx/hooks/xxxxxxxxxxxxxxxxxxxx";

return msg;

 ※msg.payload.textの@XXX.XXXはメンションです。
http requestノードの、SSL/TLS接続を有効化チェックボックスにチェックを入れ、Mattermostの証明書を設定します。
Node-RED.http.JPG
すべてのノードをつなぎ、右上のデプロイボタンを押下して終了です!
 ※debugノードの対象をmsgオブジェクト全体にして動作確認を行いました。

Botと同じ内容をメールにも送る方法

Botと同じ内容をメールで送る方法についての説明です。

手順

画面右上のメインメニューから、パレットの管理を選択します。
Node-RED.palette.JPG
ノードを追加タブで、mailと入力して検索します。
Node-RED.mail.JPG
node-red-node-emailを選んでノードを追加し、メール送信ができるようにします。
functionノードに送信したい内容や件名等を書き、ディスク容量確認コマンドを実行するノードとつなげます。

node.js
msg.payload = msg.ssh.stdout;
msg.topic = "【Node-RED】ディスク空き容量お知らせ";

return msg;

接続部が左側にあるemailノードを選び、宛先、サーバ、ポートなどを入力し、ディスク容量確認コマンドを実行するノードとつなげます。
デプロイして終了!設定した日時にメールが届きます。

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

Docker-composeで解析(RNAseq)

Docker-composeで解析(RNAseq)

これを見ればあなたもすぐにRNAseq解析ができる!!
※Dockerインストール済みの場合

前回に引き続き、解析ツールをDocker-composeで作成してみた。
Docker-composeを使ってみよう

今回やりたいこと

RNAseq解析
docker-composeでsraファイルをbamファイルにしてみせます。

手順

sra 
↓(parallel-fastq使用)
fastq
↓(fastp使用)
trim.fastq
↓(STAR使用)
bamファイル
(全てbiocontainerのコンテナを使用)

biocontainersのリポジトリURL:biocontainers

内容としては、下記リンクのスクリプトと同様のことができます。※exec_bam2readcount_STAR.Rを除く。
下記スクリプトを動かすためには様々なパッケージのインストールが必要になりますね・・・。
GitHub:https://github.com/petadimensionlab/STAR

参考URL:
RNAseq解析について:遺伝子発現量解析RNA-Seq

持ち物・下準備

・リードデータ(SRR7508944.sra)
・genome
・アノテーションファイル
をホストPC内任意のディレクトリに保存。(ディレクトリ構成は下記の通り 参照)
※今回の対象データ:SRR7508944

・genomeとアノテーションファイル名を同じ名前に変更しておこう。
例)genome=TAIR10.fa、annotation=TAIR10.gff3

参考URL:
持ち物の中身の詳細:STAR による RNA-Seq リードのマッピングSTAR (A. thaliana, paired-end RNA-Seq)

ディレクトリ構成は下記の通り

.(/yourlocal_dir)
├── docker-compose.yml
├── .env
├── input/       # リードデータ.sra保存
├── (output/)   # 解析結果の出力ファイル用ディレクトリ(docker-composeをスタートさせると作成)
└──  STARidx/  #genome、アノテーション保存 (STARindexの保存先)

Docker-compose.yml 作成

docker-compose.ymlの中身

docker-compose.yml
#env in .env file
version: "3"
services:
  parallel-fastq:
    image: quay.io/biocontainers/parallel-fastq-dump:${VER_1}
    container_name: parallel-fastq
    tty: true
    volumes:
      - ${DIR}:/tmp/wk
    working_dir: /tmp/wk
    command: >
      bash -c "parallel-fastq-dump --sra-id input/${ID}.sra --threads ${THREADS} --split-files --gzip &&
      mkdir -p output/${ID} &&
      mv *.fastq.gz output/${ID}/"

  fastp:
    image: quay.io/biocontainers/fastp:${VER_2}
    container_name: fastp
    tty: true
    volumes:
      - ${DIR}:/tmp/wk
    working_dir: /tmp/wk
    command: sh wait-for-it.sh output/${ID}/${ID}_1.fastq.gz fastp -w ${THREADS} -h output/${ID}/${ID}.html -j output/${ID}/${ID}.json -i output/${ID}/${ID}_1.fastq.gz -I output/${ID}/${ID}_2.fastq.gz -o output/${ID}/${ID}_trim_paired_1.fastq.gz -O output/${ID}/${ID}_trim_paired_2.fastq.gz

  star:
    image: quay.io/biocontainers/star:${VER_3}
    container_name: star
    tty: true
    volumes:
      - ${DIR}:/tmp/wk
    working_dir: /tmp/wk
    command: >
      bash -c "sh wait-for-it.sh output/${ID}/${ID}.html STAR --runThreadN ${THREADS} --runMode genomeGenerate --genomeDir STARidx --genomeFastaFiles STARidx/${SPECIES}.fa --sjdbGTFfile STARidx/${SPECIES}.gff3 ${STARidxopt} &&
      STAR --runThreadN  ${THREADS} --readFilesCommand gunzip -c --genomeDir STARidx --readFilesIn output/${ID}/${ID}_1.fastq.gz output/${ID}/${ID}_2.fastq.gz --outFileNamePrefix output/${ID}/${ID}. ${STARopt}"

.envの中身

.env
VER_1=0.6.5--py_0 #バージョン
VER_2=0.20.0--hdbcaa40_0 #バージョン
VER_3=2.7.1a--0 #バージョン
THREADS=6 #スレッド
DIR=/yourlocal_dir #ローカルのディレクトリ
ID=SRR7508944 #対象データのID
SPECIES=TAIR10 #対象データの種類
STARidxopt=--sjdbOverhang 100 --genomeSAindexNbases 12 #STARコマンドのオプション
STARopt=--outFilterMultimapScoreRange 1 --outFilterMultimapNmax 10 --outFilterMismatchNmax 5 --alignIntronMax 500000 --alignMatesGapMax 1000000 --sjdbScore 2 --alignSJDBoverhangMin 1 --genomeLoad NoSharedMemory --limitBAMsortRAM 0 --outFilterMatchNminOverLread 0.33 --outFilterScoreMinOverLread 0.33 --sjdbOverhang 100 --outSAMstrandField intronMotif --outSAMattributes NH HI NM MD AS XS --outSAMunmapped Within --outSAMtype BAM Unsorted --outSAMheaderHD @HD VN:1.4 #STARコマンドのオプション

さて、docker-compose.ymlの解説です。

ざっくり解説すると、
 3つのコンテナイメージをコンテナ化し (=parallel-fastq・fastp・star)
 マウントするディレクトリはどれも同じで (=ホストは.envに記載した”/yourlocal_dir”・コンテナはそれぞれtmp/wk)
 それぞれコマンドを与えている。(=それぞれcommand以下に記述)

それぞれのコマンドのざっくり解説は下記にて。

内容→SRA Toolkitのfastq-dumpを並列実行して高速化する parallel-fastq-dump

・parallel-fastq
 SRR7508944.sra から SRR7508944_1.fastq.gz・SRR7508944_2.fastq.gz を出力。

内容→高速なfastqの前処理パイプライン fastp

・fastp
 SRR7508944_1.fastq.gz・SRR7508944_2.fastq.gz から SRR7508944_trim_paired_1.fastq.gz・SRR7508944_trim_paired_2.fastq.gz・SRR7508944.json・SRR7508944.html を出力。

内容→
高速なRNA seqのマッピングツール STAR

・star
1. index作成。
2. SRR7508944_trim_paired_1.fastq.gz・SRR7508944_trim_paired_2.fastq.gz から SRR7508944.Aligned.out.bam・そのたlogファイル を出力。

sraファイルがbamファイルになりましたね。

動かしてみよう!

genomeとか探すんめんどくせ。って方、下記Githubからどぞー!
.env の/yourlocal_dirを変更するだけ!
※sraは容量でかいのでアップロードできませんでした。自力でダウンロードしてください。(docker run --rm -v /yourlocal_dir/Docker_compose_STAR/input:/root/ncbi/public/sra inutano/sra-toolkit prefetch SRR7508944でダウンロードできます。)

Github:https://github.com/petadimensionlab/Docker_compose_STAR

動かし方

まず、docker-conpose.ymlのあるディレクトリに移動。
cd /yourlocal_dir

起動
イメージがない場合は自動でインストールしてくれます。
docker-compose up -d

logをみる。
途中経過もわかる。
docker-compose logs

終わったらおしまい。
いらないので捨てましょう。 
docker-compose rm -f

これであなたもRNAseq解析ができた(はず)!!

もっと詳しくDocker-compose.ymlを知りたい方

fastpとstarのcommandに書いてるwait-for-it.shってなに?

→順番にコンテナのコマンドが動作するようにシェルスクリプトです。(いっぺんにコンテナが動き出すといろいろやばいです。)

wait-for-it.shの中身

wait-for-it.sh
#!/bin/sh

set -e

waitfile="$1"
shift
cmd="$@"

until test -e $waitfile
do
    >$2 echo "Waiting for file [$waitfile]."
    sleep 1
done

>&2 echo "Found file [$waitfile]."
exec $cmd

command記入例

  コンテナ①:
  (略)
    command: touch test.txt 
  コンテナ②:
  (略)
    command: sh wait-for-it.sh test.txt touch test2.txt

説明:コンテナ①でできる予定のファイル(text.txt)の存在を確認するまでコンテナ②は待機、ファイル(text.txt)見つけたら指定したコマンド(touch test2.txt)動かす。

参考URL:docker-composeでDBの起動完了を待ってからWebアプリを実行する

tty: ture ってなに?

→起動したコンテナが落ちないようにするためです。

参考URL:docker-compose up したコンテナを起動させ続ける方法

そのた

STARを使用しているので、PCのメモリ・CPUをよく確認して動かしましょう。
→仮想環境でも自分のPCのスペック以上のことはできません。。。

スペック最強PC!!なのに動きません。
→Dockerの設定を変更しましょう。
 右上(Macの場合)のクジラクリック→Preferences → Advancedで設定できます。
参考URL: macOSでDocker環境構築

7.おまけのおまけ

沢山sampleがデータあるとき用のpythonスクリプトを作成しました。
このpythonを動かしてほったらかしにできますzzz(( _ _ ))..zzzZZ
※ついでにSRAデータもinsallできる特典付き!!
※まさかの未完!!(:pick:失敗したので工事中:pick:

おわり

おわりではないけどおわり。
工事終了しましたら、「おまけのおまけ」が更新されます。

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

Railsコンソールで日本語入力ができない現象【Docker】

はじめに

Railsの参考書を読みながら勉強を進めているときに、

DockerのRailsコンテナ内で、Railsコンソールを起動し、日本語文章を入力しようとしたら、
入力はできるのに、エンターキーを押すとターミナルに表示されないという現象が起きたので、

その解決方法についてです。

エンターキーを押す前
image.png

押した後
image.png

解決方法

DockerFileに、

ENV LANG C.UTF-8

を記入して

docker-compose up --buildで再起動

これでコンソールに日本語を入力できるようになります。

エンターキーを押す前
image.png

押した後
image.png

参考にしたページ:Docker / rails console で日本語入力できない問題
https://gist.github.com/tasiyo7333/2163a09129ed36639645145a0146d8d3

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

Docker-composeを使ってみよう

Docker-compose とは

ちょっと調べてみました。
複数のDockerコンテナをまとめて自動化・管理するツールらしい。
連携(コンテナ間のネットワーク連携)もできるらしい。

参考URL:
Docker compose ことはじめハンズオン

Docker-compose インストール

Docker Desktop for Mac でDockerをinstallした場合は不要。(Windowsも同じく)
Linuxの場合は上記参考URLを参照。(丸投げ)

Docker-compose.yml 作成

ディレクトリ構成は下記の通り

.(/yourlocal_dir)
├── .env
├── Dockerfile 
├── docker-compose.yml
├── input/       
├── output/    
└── script/     

docker-compose.ymlの基本のき

「見本のdocker-conpose.yml」

docker-conpose.yml
version: "3" # バージョン
services: # 起動するコンテナの定義
  ubuntu:
    image: ubuntu:16.04 # 使いたいイメージをinstall
    container_name: ubuntu # コンテナの名前
    volumes:
      - /yourlocal_dir:/tmp/wk #hostとマウント
    working_dir: /tmp/wk #コンテナ内のワークディレクトリ
    command: touch test.txt # コマンド記入

image:・・・使いたいイメージを指定する...ではなく作成したdockerfileを指定することもできる。image:のところをbuild: .に変更でおk。
command: デフォルトのコマンドを上書きしますので要注意。
 複数にコマンドを記入したい場合は下記の通り。

    command: >
      bash -c "touch test.txt &&
      mkdir output/test &&
      mv test.txt output/test/"

はい、できた。
さて、「見本のdocker-conpose.yml」 を動かすと/yourlocal_dir/の中に test.txt ができます。

参考URL:Docker Compose - docker-compose.yml リファレンス

これだけは覚えとけ!docker-composeコマンド

まず、docker-conpose.ymlのあるディレクトリに移動。
cd /yourlocal_dir

起動
イメージがない場合は自動でインストールしてくれます。
今回の場合、瞬殺でtest.txtが出来上がります。
docker-compose up -d

Dockerfileをbuildする場合はこちら。
docker-compose up --build

コンテナの一覧 
docker-compose ps

止める
docker-compose stop

再起動
docker-compose restart

logをみる。
docker-compose logs

コンテナの中に入ってみる。
docker-compose exec [コンテナ名] /bin/bash
出るときは[control+C]

捨てる
お疲れ様でした。 
-f は強制終了。つけない場合は Are you sure? [yN] と聞かれる。
docker-compose rm -f

複数のコンテナの内、1つだけ止めたい!という場合は下記の通り。
下記ルールは他docker-composeコマンドでも使えるようですよ。
docker-compose stop [container_name]

参考URL:docker-compose コマンドまとめ

ちょっと上級者編(筆者的に)

環境変数(.env) について

スマートに書きたい。
バージョンなど変更する場合がある値。
同じ文字打つのめんどい。
という時に!

command
#テキストエディタで開いて
nano .env
.env
#環境変数を入力して保存
VER=16.04
DIR=/home/peta/ws
CMD=touch test_20191107.txt

docker-conpose.ymlはこんな感じ

yaml=docker-conpose.yml
version: "3" # バージョン
services: # 起動するコンテナの定義
  ubuntu:
    image: ubuntu:${VER} # 使いたいイメージをinstall
    container_name: ubuntu # コンテナの名前
    volumes:
      - ${DIR}:/tmp/wk #hostとマウント
    working_dir: /tmp/wk #コンテナ内のワークディレクトリ
    command: ${CMD} # コマンド記入

他のPCや仲間に共有したときに、.envを変更するだけでいいようにdocker-compose.ymlを書いといたらいいですね。
参考URL:docker-compose.ymlで.envファイルに定義した環境変数を使う

別にDockerだけでよくね? という方へ

筆者の個人的感想です。
【利点】
・いろんなコンテナを同時に使える。組み合わせられる。
・docker-compose.ymlへ、コマンドや設定を書いているので、のちのち管理しやすい。
・ymlで書くのでわかりやすい(個人的見解)
 →後から見やすいかな?と思いました。
・docker-compose.ymlを渡すだけでまるまる共有できる!!!再現できる!!!(docker-composeコマンド使えたら)

【問題点】
・docker-compose.yml 書くのに時間かかりそう。(コンテナの組み合わせを考えたりも)
 →時間かかりますが、一度できたら流用しやすそう。

【まとめ】
共有・再現する際に非常に役立ちそう。


おわり。

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

インフラエンジニアがGitLabを建ててCI/CDを動かせるようにするまで

経緯

インフラエンジニアとして入社し、AWS案件に携わってからCloudFormationやLambda用のPythonを書くことが増えました。
Git/GitHub/GitLabの名前は聞いたことがあるレベルでしたが、前述のバージョン管理が必須になり、自社のGitLabを活用することになります。
素人でしたが直ぐに利便性を感じ、自宅にあるESXiにDockerの勉強がてらGitLabを立てればよいのではと考えたのがスタートとなります。
プライベートで自宅のデスクトップPCと外出先で使うノートPCのファイルを扱うのに重宝しています。

自宅のESXiに建てている為、それが壊れてしまうと0から構築方法を調べ事になるのを回避したいのでここに忘備録として記載していきます。

Docker

GitLabをDocker-composeで建てたい為、Dockerをインストールします。
※余談ですがk8sをインストールする際にsnapのDockerに苦労したので、下記の方法でDockerをインストールしています。k8sを触らない方はsnapでも問題ないかと思います。

環境

項目
OS Ubuntu 19.04

※2019/12月時点でk8sがdockerのver.19に対応していない為、バージョン指定でインストールしています。

sudo addgroup --system docker
sudo usermod -aG docker user01
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt-get update
apt-cache madison docker-ce
sudo apt-get install docker-ce=5:18.09.9~3-0~ubuntu-bionic docker-ce-cli=5:18.09.9~3-0~ubuntu-bionic containerd.io
sudo systemctl start docker
sudo systemctl enable docker

GitLab

Git Labはバージョン管理ツールですが、他にも便利な機能が備わっています。
詳しくは公式サイトをご覧ください。

  • Git以外の機能
    • CI/CD
    • Issues
    • Wiki
    • etc

GItLab DockerCompose

以下のファイルをダウンロードし、別のPCからもアクセスしたい場合は
GITLAB_HOST=をローカルのIPアドレスへ変更してください。

https://github.com/sameersbn/docker-gitlab/blob/master/docker-compose.yml

version: '2'

services:
  redis:
    restart: always
    image: sameersbn/redis:4.0.9-2
    command:
    - --loglevel warning
    volumes:
    - redis-data:/var/lib/redis:Z

  postgresql:
    restart: always
    image: sameersbn/postgresql:10-2
    volumes:
    - postgresql-data:/var/lib/postgresql:Z
    environment:
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production
    - DB_EXTENSION=pg_trgm

  gitlab:
    restart: always
    image: sameersbn/gitlab:12.5.2
    depends_on:
    - redis
    - postgresql
    ports:
    - "10080:80"
    - "10022:22"
    volumes:
    - gitlab-data:/home/git/data:Z
    environment:
    - DEBUG=false

    - DB_ADAPTER=postgresql
    - DB_HOST=postgresql
    - DB_PORT=5432
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production

    - REDIS_HOST=redis
    - REDIS_PORT=6379

    - TZ=Asia/Kolkata
    - GITLAB_TIMEZONE=Kolkata

    - GITLAB_HTTPS=false
    - SSL_SELF_SIGNED=false

    - GITLAB_HOST=localhost
    - GITLAB_PORT=10080
    - GITLAB_SSH_PORT=10022
    - GITLAB_RELATIVE_URL_ROOT=
    - GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphanumeric-string
    - GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
    - GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alphanumeric-string

    - GITLAB_ROOT_PASSWORD=
    - GITLAB_ROOT_EMAIL=

    - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
    - GITLAB_NOTIFY_PUSHER=false

    - GITLAB_EMAIL=notifications@example.com
    - GITLAB_EMAIL_REPLY_TO=noreply@example.com
    - GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com

    - GITLAB_BACKUP_SCHEDULE=daily
    - GITLAB_BACKUP_TIME=01:00

    - SMTP_ENABLED=false
    - SMTP_DOMAIN=www.example.com
    - SMTP_HOST=smtp.gmail.com
    - SMTP_PORT=587
    - SMTP_USER=mailer@example.com
    - SMTP_PASS=password
    - SMTP_STARTTLS=true
    - SMTP_AUTHENTICATION=login

    - IMAP_ENABLED=false
    - IMAP_HOST=imap.gmail.com
    - IMAP_PORT=993
    - IMAP_USER=mailer@example.com
    - IMAP_PASS=password
    - IMAP_SSL=true
    - IMAP_STARTTLS=false

    - OAUTH_ENABLED=false
    - OAUTH_AUTO_SIGN_IN_WITH_PROVIDER=
    - OAUTH_ALLOW_SSO=
    - OAUTH_BLOCK_AUTO_CREATED_USERS=true
    - OAUTH_AUTO_LINK_LDAP_USER=false
    - OAUTH_AUTO_LINK_SAML_USER=false
    - OAUTH_EXTERNAL_PROVIDERS=

    - OAUTH_CAS3_LABEL=cas3
    - OAUTH_CAS3_SERVER=
    - OAUTH_CAS3_DISABLE_SSL_VERIFICATION=false
    - OAUTH_CAS3_LOGIN_URL=/cas/login
    - OAUTH_CAS3_VALIDATE_URL=/cas/p3/serviceValidate
    - OAUTH_CAS3_LOGOUT_URL=/cas/logout

    - OAUTH_GOOGLE_API_KEY=
    - OAUTH_GOOGLE_APP_SECRET=
    - OAUTH_GOOGLE_RESTRICT_DOMAIN=

    - OAUTH_FACEBOOK_API_KEY=
    - OAUTH_FACEBOOK_APP_SECRET=

    - OAUTH_TWITTER_API_KEY=
    - OAUTH_TWITTER_APP_SECRET=

    - OAUTH_GITHUB_API_KEY=
    - OAUTH_GITHUB_APP_SECRET=
    - OAUTH_GITHUB_URL=
    - OAUTH_GITHUB_VERIFY_SSL=

    - OAUTH_GITLAB_API_KEY=
    - OAUTH_GITLAB_APP_SECRET=

    - OAUTH_BITBUCKET_API_KEY=
    - OAUTH_BITBUCKET_APP_SECRET=

    - OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL=
    - OAUTH_SAML_IDP_CERT_FINGERPRINT=
    - OAUTH_SAML_IDP_SSO_TARGET_URL=
    - OAUTH_SAML_ISSUER=
    - OAUTH_SAML_LABEL="Our SAML Provider"
    - OAUTH_SAML_NAME_IDENTIFIER_FORMAT=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
    - OAUTH_SAML_GROUPS_ATTRIBUTE=
    - OAUTH_SAML_EXTERNAL_GROUPS=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_USERNAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME=

    - OAUTH_CROWD_SERVER_URL=
    - OAUTH_CROWD_APP_NAME=
    - OAUTH_CROWD_APP_PASSWORD=

    - OAUTH_AUTH0_CLIENT_ID=
    - OAUTH_AUTH0_CLIENT_SECRET=
    - OAUTH_AUTH0_DOMAIN=
    - OAUTH_AUTH0_SCOPE=

    - OAUTH_AZURE_API_KEY=
    - OAUTH_AZURE_API_SECRET=
    - OAUTH_AZURE_TENANT_ID=

volumes:
  redis-data:
  postgresql-data:

dockerの起動

docker-compose up -d

するだけです。
port10080が空いていればアクセスができます。

CI/CD

CI/CDを動かすためにはRunnsersの設定が必要です。
RunnsersはRunnerもしくはk8sが必要になります。
今回はRunnserで構築しました。

  1. GitLabでProjectを作成
  2. Projectのページに遷移し、左側のSettings image.png
  3. CI/CD -> Runners -> Expand image.png
  4. 「Set up a specific Runner manually」に従いセットアップしていきます。

    1. インストール
      公式ページ

      sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
      sudo chmod +x /usr/local/bin/gitlab-runner
      sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
      sudo /usr/local/bin/gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
      sudo systemctl enable gitlab-runner
      sudo systemctl start gitlab-runner
      

       

    2. GitLabとの紐づけ
      公式ページ

       sudo /usr/local/bin/gitlab-runner register
       Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
       # GitLab内のCI/CD SettingsページにあるURLを貼り付け
       Please enter the gitlab-ci token for this runner
       # GitLab内のCI/CD Settingsページにあるトークンを貼り付け
       Please enter the gitlab-ci description for this runner
       # GitLab内の表示名
       Please enter the gitlab-ci tags for this runner (comma separated):
       # GitLab内のタグ名
       Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
       docker #今回はdockerを使用するため、dockerと入力します。
       Please enter the Docker image (eg. ruby:2.1):
       Python:3.7 #今回はpythonを扱う為、python3.7のdocker imageを入力します。
    

    CI/CD Settingsページ内のRunners activated for this projectにRunnserが追加されれば成功です。出てこない場合は上記のGitLabとの紐づけを再度実施してみてください。

  5. 作成したProjectにCI/CDを設定

    1. Projectの直下にgitlab-ci.yml を作成
   image: python:3-alpine # docker image

   before_script: # 一番最初に実行するコマンド
     - pip install pytest pytest-cov autopep8 radon

   stages: # いくつか実行したいJobの順番を指定できる
     - build
     - test


   job1: # job名(任意)
     stage: build # stage上のbuildとして設定
     script: # listでコマンド記載
       - autopep8 -i testCode.py
       - radon mi -s testCode.py
       - radon cc -s testCode.py

   job2:
     stage: test
     script:
       - pytest -v --cov=.

上記はjob1が成功したらjob2を実行するようstagesに記載をしています。
gitlab-ci.ymlに関しては以下の記事をご参照ください。

もう一つ大事なCDはありませんが、AWS Lambdaの記事を参照頂ければと思います。

まとめ

忘備録として記載した結果、公式のコピペになってしまいました。
GitLabのCI/CDはあまりハマらず設定をすることができました。
これから開発のスピードをポジティブな意味で早くできればと考えています。(インフラエンジニアとは・・・)
公式や先駆者様の見様見真似で動かしている為、おかしな点がありましたらご教示いただけますと幸いです。

参考リンク

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

【Nim】とりあえずにむにむするための環境構築

(ノ)・ω・(ヾ)ニムニム

??遅刻しました??
慌てて急いで書いた

Nim Advent Calendar 2019の8日目の記事です。

Nimが正式リリースされ、正直なところそれまで聞いたことなかったのですがせっかくなので触ってみたいと思いました。
しかし、環境構築めんどくさいなーと思ってたらDocker Hubに普通にあったのでそれを使ってとにかくNimを書いてみる為の環境を作っていきます。

Docker Hub
https://hub.docker.com/r/nimlang/nim

用意するもの

  • パソコン(大事)
  • git
  • Docker
  • Docker Compose

手順

$ git clone https://github.com/SUGURUHASEGAWA/nimnim.git
$ cd nimnim
$ docker-compose up -d --build
$ docker-compose exec app nimble init ​

はい終わりです。
これでNimプロジェクトと初期コードが生成されるので思う存分にむにむしてください。

解説

流石にこれだけではアレなので少しだけ解説していきます。

docker-compose.yml

docker-compose.yml
version: "3.7"
services:
  app:
    build:
      context: ./docker/nim
      dockerfile: Dockerfile
    tty: true //常時起動させたい
    privileged: true
    volumes:
      - ./workspace:/opt/${PROJECT_NAME}
    working_dir: /opt/${PROJECT_NAME}

ほぼおまじないレベルの記述ですPROJECT_NAME.envファイルに定義されているので好きな名前に書き換えてください。

Dockerfile

Dockerfile
FROM nimlang/nim:latest

RUN apt-get update && apt-get install -y \
  vim \
  curl \
  mingw-w64

これもほぼおまじないですね、
Docker Hubにあるコンテナのlatestバージョンを指定していますが、このVerはお好みで変えてください。
また、mingw-w64はWindows向けにクロスコンパイルするためにインストールしていますが、今回はとりあえず言語にふれるまでなので割愛します。
まだクロスコンパイルをちゃんとやってない

クロスコンパイルについては下記ページを参照するといいかも?
https://nim-lang.org/docs/nimc.html

nimble init

コンテナを起動したら、コンテナ上でnimble initを実行してnimのプロジェクトを作成します。
実行すると対話形式でいくつか質問されるのでお好みで設定してください。(基本的に後からでも編集できます)

質問内容

Your name? [Anonymous]
自分の名前を入力。
※未入力の場合はAnonymousになります。

Package type?
Library・・・別アプリケーションに機能を提供するライブラリを作成する場合
Binary・・・エンドユーザ向けのアプリケーションを作成する場合
Hybrid・・・未確認

Initial version of package? [0.1.0]
初期Verを設定します
※未入力の場合は0.1.0

Package description? [A new awesome nimble package]
パッケージの説明を入力。

Prompt: Package License?
パッケージのライセンスを選択。
MIT等いくつか選択肢が出てくるのでお好みのものを

Hello, World!

※Package typeでBinaryを選択したものとします

workspace配下のsrcディレクトリ内にあるnimファイルを見てみると以下の様に記述されています

# This is just an example to get you started. A typical binary package
# uses this file as the main entry point of the application.

when isMainModule:
  echo("Hello, World!")

既にこんにちはしてくれているので、ありがたくこれを使わせてもらいましょう。
※編集する際はこのファイルを基準に記述してください

プロジェクトにビルドは下記コマンドを実行してください

docker-compose exec app nimble build

コマンドを実行すると、workspace配下にバイナリファイルができているので、実行すると無事こんにちはできます

$ ./nimnim
Hello, World!

あとがき

Nimの環境構築は正直Dockerを使わなくてもそこまで難しい手順はありませんが、
最近個人的に使用するPCがコロコロ変わるので、いちいちNimをインストールするのが手間だったので、Dockerでの環境構築をしてみました。
(コンテナ持ってきて起動してるだけなので構築と言っていいものか)

クロスコンパイルはよく分からなくて手つかずですが、とりあえずNim言語を触って見るぶんにはコスト低く初めれるんじゃないかと思います。

(ノ)・ω・(ヾ)ニムニム

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

サイボウズOfficeのワークフローをPuppeteerでSlackに通知してみた

はじめに

この記事は 株式会社ピーアールオー(あったらいいな!を作ります) Advent Calendar 2019 の9日目の記事です。
今回は最近困っていることを解決してみようと思います。

サイボウズOfficeのワークフローにすぐ気づけない...

サイボウズOfficeでワークフローなどの申請が来た場合、すぐに気づける方法はいくつかあります。
- ブラウザ開いてのホーム画面で確認
- パソコン用リマインダーツールを使う(Cybozu Desktop)
- メールを確認する

が、私は基本vscodeとslackばかり見ているので、すぐに気づかずSlackで突っ込まれることが多々あります...
なので、直ぐに気づくことができるよう、ワークフローの申請があった際にSlackに通知するツールを作ってみようと思います。

どうやって作る?

サイボウズOfficeのAPIを叩いてワークフローの状況を定期的に参照してSlackに通知しよう!
と、思ったのですが、私が調べた限りではサイボウズOfficeはAPI使えないみたいです。
(kintonegaroonはAPI使えるようです。)

なので、ヘッドレスでブラウザを操作できるPuppeteerを使って、ワークフローの状況を参照、slackに通知していきたいと思います。

処理の流れ

  1. dockerでPuppeteer用の環境構築 & 監視用スクリプト稼働
  2. サイボウズOfficeへアクセスして、ワークフローの状況を取得
  3. docker(Puppeteer)から、SlackのIncoming Webhookにリクエストを送信
  4. Incoming Webhook がSlackへ通知

dockerでPuppeteer環境を作る

dockerで開発環境を作っていきます。
まずは、dockerfile。Puppeteer Troubleshootingのdockerのコピペです。

FROM node:10-slim

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
    --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-unstable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
    && mkdir -p /home/pptruser/Downloads \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /node_modules

# Run everything after as non-privileged user.
USER pptruser

# CMD ["google-chrome-unstable"]”

次はdocker-compose。
先ほどのdockerfileでコンテナを参照。
ワークフロー監視用のjsを共有後、homeディレクトリにコピーして実行するようにしています。
(共有ディレクトリだと上手く動かなかったので。)

参照先のサイボウズの設定なども環境変数に持たせて変更できるようにしました。
参照先はサイボウズOfficeデモサイト
監視間隔5秒で設定しています。

デモサイトなので、パスワードなし。
ログインIDはchromeデベロッパーツールでログインユーザのセレクトボックス(option値)を参照して記載しています。
Basic認証の設定も入れました。処理上は作成しますが、今回はデモサイトで認証がないので適当な値を入れます。

version: '3.3'
services:
  puppeteer:
    build:
      context: ./
      dockerfile: Dockerfile
    volumes:
      - ./checkWorkflow.js:/opt/checkWorkflow.js
    environment:
      - CYBOZU_URL=https://onlinedemo.cybozu.info/scripts/office10/ag.cgi?
      - IS_BASIC=0
      - BASIC_ID=xxxx
      - BASIC_PW=xxxx
      - LOGIN_ID=17
      - LOGIN_PW=
      - SLACK_CHANNEL=@xxxxxxx
      - SLACK_ENTRY_POINT=/services/xxxxx/xxxxx/xxxxxxxxxxxxxxxxx
      - LOOP_TIME=5000
    command: sh -c "cp /opt/checkWorkflow.js /home/pptruser/. && node /home/pptruser/checkWorkflow.js"
    tty: true

環境変数一覧

環境変数 内容
CYBOZU_URL サイボウズのURL
IS_BASIC Basic認証有無(0:なし、1:あり)
BASIC_ID Basic認証 ID
BASIC_PW Basic認証 Password
LOGIN_ID サイボウズログインID
LOGIN_PW サイボウズログインPW
SLACK_CHANNEL 検知時送信先 Slackチャンネル
SLACK_ENTRY_POINT 検知時送信先 Slack送信先URL(パス)
LOOP_TIME 監視間隔

開発自体はcommandをコメントアウトして、VSリモートで作成したコンテナにアクセスして開発しました。

Puppeteerで申請状況を取得する

ログイン

docker-composeで設定した環境変数を利用してBasic認証とログインを行います。
「page.select」と「page.type」でログインフォームにログインユーザとパスワードを設定。
「document.querySelector('input[name="Submit"]').click()」でログインボタンを押します。

  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();

  // Basic認証
  if (IS_BASIC === '1')
    await page.authenticate({ username: BASIC_ID, password: BASIC_PW });

  // サイボウズアクセス
  await page.goto(CYBOZU_URL, {
    waitUntil: 'domcontentloaded'
  });

  // ログイン設定
  await page.select('select[name="_ID"]', LOGIN_ID);
  await page.type('input[name="Password"]', LOGIN_PW);

  // ログイン
  await page.evaluate(() => {
    document.querySelector('input[name="Submit"]').click();
  });

  // ログイン後画面が読み込まれるまで待機
  await page.waitForNavigation({
    timeout: 30000,
    waitUntil: 'domcontentloaded'
  });

ワークフロー取得

ワークフロー画面遷移後、受信一覧を参照します。
最新の申請の番号を取得し、チェック済み配列に格納します。
次回以降は申請済配列を参照し、新規番号がある場合は後続の処理(Slack通知)に繋げるようにしています。

 // ワークフロー画面へ
  await page.goto(CYBOZU_URL + 'page=WorkFlowRecept');

  // 受信一覧取得
  const list = await page.$$('table.dataList > tbody  > tr');

  // 申請があるか判定判定
  if (list.length <= 1) {
    await browser.close();
    console.log("nothing ")
    return;
  }

  // 最新の申請取得
  const td = await list[1].$('td')
  // 申請番号取得
  const newAppNumber = (await (await td.getProperty('textContent')).jsonValue()).replace(/\r?\n/g, '');

  // ブラウザ終了
  await browser.close();

  // チェック済みの番号か判定
  if (appNumberList.indexOf(newAppNumber) >= 0) {
    console.log("checked number : " + newAppNumber);
    return;
  } else {
    appNumberList.push(newAppNumber);
    console.log("new number : " + newAppNumber);
  }

Slackに通知する

SlackのIncoming Webhookを利用して通知します。
Incoming Webhooksについては、以下など、説明されている記事が沢山あると思いますので、割愛します。
参考: SlackのIncoming Webhooksを使い倒す

環境変数に設定したSlackのチャンネル、URLを設定してPOSTします。

 let postData = {
    channel: SLACK_CHANNEL,
    username: 'work-flow-checker',
    text: '新規申請があります!',
    icon_emoji: ':ghost:'
  };
  let postDataStr = JSON.stringify(postData);

  let options = {
    host: 'hooks.slack.com',
    // port: 80,
    path: SLACK_ENTRY_POINT,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postDataStr)
    }
  };

  let req = http.request(options, res => {
    console.log('STATUS: ' + res.statusCode);
    console.log('HEADERS: ' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', chunk => {
      console.log('BODY: ' + chunk);
    });
  });
  req.on('error', e => {
    console.log('problem with request: ' + e.message);
  });
  req.write(postDataStr);
  req.end();

完成

ソースが完成しました。
環境変数を変数に設定後、監視用のfunctionをsetIntervalで定期的に稼働させるようにしています。
チェック済申請番号はメモリ上に保持するので、起動毎にチェック済みの申請番号が消えますが、起動しっぱなしにするので気にしません。

ファイル全量
.
├── Dockerfile
├── checkWorkflow.js
└── docker-compose.yml
checkWorkflow.js
const puppeteer = require('puppeteer');
const http = require('https');

// 設定
const CYBOZU_URL = process.env.CYBOZU_URL
const IS_BASIC = process.env.IS_BASIC
const BASIC_ID = process.env.BASIC_ID
const BASIC_PW = process.env.BASIC_PW
const LOGIN_ID = process.env.LOGIN_ID
const LOGIN_PW = process.env.LOGIN_PW
const SLACK_CHANNEL = process.env.SLACK_CHANNEL
const SLACK_ENTRY_POINT = process.env.SLACK_ENTRY_POINT
const LOOP_TIME = process.env.LOOP_TIME // ミリ秒

// チェック済申請番号格納
var appNumberList = [];

async function checkWorkflow() {
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();

  // Basic認証
  if (IS_BASIC === '1')
    await page.authenticate({ username: BASIC_ID, password: BASIC_PW });

  // サイボウズアクセス
  await page.goto(CYBOZU_URL, {
    waitUntil: 'domcontentloaded'
  });

  // ログイン設定
  await page.select('select[name="_ID"]', LOGIN_ID);
  await page.type('input[name="Password"]', LOGIN_PW);

  // ログイン
  await page.evaluate(() => {
    document.querySelector('input[name="Submit"]').click();
  });

  // ログイン後画面が読み込まれるまで待機
  await page.waitForNavigation({
    timeout: 30000,
    waitUntil: 'domcontentloaded'
  });

  // ワークフロー画面へ
  await page.goto(CYBOZU_URL + 'page=WorkFlowRecept');

  // 受信一覧取得
  const list = await page.$$('table.dataList > tbody  > tr');

  // 申請があるか判定判定
  if (list.length <= 1) {
    await browser.close();
    console.log("nothing ")
    return;
  }

  // 最新の申請取得
  const td = await list[1].$('td')
  // 申請番号取得
  const newAppNumber = (await (await td.getProperty('textContent')).jsonValue()).replace(/\r?\n/g, '');

  // ブラウザ終了
  await browser.close();

  // チェック済みの番号か判定
  if (appNumberList.indexOf(newAppNumber) >= 0) {
    console.log("checked number : " + newAppNumber);
    return;
  } else {
    appNumberList.push(newAppNumber);
    console.log("new number : " + newAppNumber);
  }

  // SlackへのPOST用データ作成
  let postData = {
    channel: SLACK_CHANNEL,
    username: 'work-flow-checker',
    text: '新規申請があります!',
    icon_emoji: ':ghost:'
  };
  let postDataStr = JSON.stringify(postData);

  // POST設定
  let options = {
    host: 'hooks.slack.com',
    // port: 80,
    path: SLACK_ENTRY_POINT,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postDataStr)
    }
  };

  // POST
  let req = http.request(options, res => {
    console.log('STATUS: ' + res.statusCode);
    console.log('HEADERS: ' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', chunk => {
      console.log('BODY: ' + chunk);
    });
  });
  req.on('error', e => {
    console.log('problem with request: ' + e.message);
  });
  req.write(postDataStr);
  req.end();
};

setInterval(function () {
  checkWorkflow();
}, LOOP_TIME);

動かしてみる

それでは動かしていきます!

デモサイトの申請状況

ワークフロー(受信一覧)_-_サイボウズ_Office.png

docker起動

 docker-compose up
Starting cybozu-puppeteer_puppeteer_1 ... done
Attaching to cybozu-puppeteer_puppeteer_1
puppeteer_1  | new number : 11 
puppeteer_1  | STATUS: 200
puppeteer_1  | HEADERS: {"content-type":"text/html","transfer-encoding":"chunked","connection":"close","date":"Sun, 08 Dec 2019 21:07:31 GMT","server":"Apache","vary":"Accept-Encoding","strict-transport-security":"max-age=31536000; includeSubDomains; preload","referrer-policy":"no-referrer","x-frame-options":"SAMEORIGIN","access-control-allow-origin":"*","x-via":"haproxy-www-ow8r","x-cache":"Miss from cloudfront","via":"1.1 89e14ce757792ac369341dc84fa01d52.cloudfront.net (CloudFront)","x-amz-cf-pop":"NRT57-C2","x-amz-cf-id":"p0yzt6kUDK3Nk1tuxr7ojy1tWrIgTG8j1F8m2Euu0pIPnxj9kV-gug=="}
puppeteer_1  | BODY: ok
puppeteer_1  | checked number : 11 

new numberとして、11番の申請が検知されました。
Slackの方にも通知がきました。

Slack___Slackbot___PRO-D-Sol_と_「サイボウズOfficeのワークフローをPuppeteerでSlackに通知してみた」を編集_-_Qiita.png

デモサイトで追加申請をあげてみます。
新規申請が追加されることで、再度Slackに通知が来るはずです。

申請追加
ワークフロー(受信一覧)_-_サイボウズ_Office.png

Slackを確認。追加で申請がきました!

Slack___Slackbot___PRO-D-Sol_と_「サイボウズOfficeのワークフローをPuppeteerでSlackに通知してみた」を編集_-_Qiita.png

感想

普段サイボウズの通知見逃しが多いので作ってみました。
エラーハンドリングなどあまり考慮せずバッと作ったので、エラー沢山あるかもしれません...
実際に使ってみて、少しずつ直して行く予定です。これで、申請漏れなどなくなることを祈ってます!

今回作成したソース

よろしければ使ってみてください。
https://github.com/hiro-kane/cybozu-check-tool

参考

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

Docker 環境に Sftp コンテナを構築して Laravel と連携する

Laravel2 Advent Calendar 2019 - Qiita の 9日目 の記事です。

サンプルリポジトリ

https://github.com/ucan-lab/laravel6-sftp

前準備

まずはLaravelの環境を用意します。

$ git clone git@github.com:ucan-lab/docker-laravel.git laravel6-sftp
$ cd laravel6-sftp
$ make create-project

http://127.0.0.1:10080

できました。

詳しい構築方法は過去記事を参考ください。

Sftpコンテナを作成する

ベースコンテナは atmoz/sftp 使用します。

対向先のディレクトリを作成する

$ mkdir sftp-store
$ echo "hello" > sftp-store/world.txt

認証用の公開鍵、秘密鍵を作成する

$ mkdir .ssh
$ ssh-keygen -t rsa -b 4096 -N "" -f .ssh/ssh_host_rsa_key

Git管理対象外設定をする

.gitignore

/.ssh
/sftp-store

鍵ファイルやSftp内のデータはGit管理したくないため。

docker-compose.yml

services:
  app:
    volumes:
      - ./.ssh/ssh_host_rsa_key:/root/.ssh/ssh_host_rsa_key

  sftp-server:
    image: atmoz/sftp
    volumes:
      - ./sftp-store:/home/foo/share
      - ./.ssh/ssh_host_rsa_key.pub:/home/foo/.ssh/keys/ssh_host_rsa_key.pub
    command: foo::1001

docker-compose.yml の差分を抜き出してます。
sftp-server に公開鍵を配置して、共有ディレクトリ(/home/foo/share)にローカルディレクリ(./sftp-store) をマウントします。

Sftp コンテナを構築

$ docker-compose down
$ docker-compose up -d --build

Laravel で Sftp 設定を行う

$ docker-compose exec app ash

app コンテナ内で実行します。

Sftpライブラリ導入

$ composer require league/flysystem-sftp

SftpServiceProvider を作成する

$ php artisan make:provider SftpServiceProvider

app/Providers/SftpServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use League\Flysystem\Sftp\SftpAdapter;

class SftpServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Storage::extend('sftp', function ($app, $config) {
            return new Filesystem(new SftpAdapter($config));
        });
    }
}

Filesystem に sftp 用のアダプタを追加します。

作成した SftpServiceProvider を登録する

config/app.php

    'providers' => [
        // ...

        App\Providers\SftpServiceProvider::class,
    ],

Sftp ディスク設定を追加する

config/filesystems.php

    'disks' => [
        'sftp-disk' => [
            'driver' => 'sftp',
            'host' => 'sftp-server',
            'port' => 22,
            'username' => 'foo',
            'privateKey' => '/root/.ssh/ssh_host_rsa_key',
            'root' => 'share',
            'timeout' => 10,
            'directoryPerm' => 0755,
        ],
    ],

お試し

$ php artisan tinker
>>> Storage::disk('sftp-disk')->get('world.txt');
=> "hello\n"

差分のコード

参考

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

Docker を使用するための最初の設定

手元で空いている端末が SD カードの容量が少ない Raspberry Pi 3 B+ (以下 Raspberry Pi)だけだったので、別途 Ubuntu サーバ上に Docker CE をインストールしておき、 Raspberry Pi には docker-ce-cli だけをインストールして、コンテナを作成できるようにします

Ubuntu サーバ上での設定

Ubuntu サーバへは Raspberry Pi から SSH でログインできるように事前に準備しておきます

/etc/systemd/system/docker.service.d/override.conf を作成して、--data-root 等の環境にあった設定をしておきます

docker-ce-cli は Raspberry Pi 上で実行しますが、ここでは -H tcp://0.0.0.0:2375 のように直接外部からアクセスするための -H オプションの指定は不要です

$ cat /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --data-root /var/lib/docker

後は、設定を反映させて docker.service を再起動しておきます

$ systemctl daemon-reload 
$ systemctl restart docker.service

Raspberry Pi 上での設定

docker-ce-cli の deb を取得して、インストールします

$ curl -LO https://download.docker.com/linux/raspbian/dists/buster/pool/stable/armhf/docker-ce-cli_19.03.5~3-0~raspbian-buster_armhf.deb
$ sudo dpkg -i docker-ce-cli_19.03.5~3-0~raspbian-buster_armhf.deb

これで docker-ce-cli は使用できるようになりましたが、Raspberry Pi 上に Docker daemon はいないためこのままでは使用できません

$ docker info
Client:
 Debug Mode: false

Server:
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
errors pretty printing info

そのため、docker context でエンドポイントが Ubuntu サーバのコンテキストを作成して、そのコンテキストに切り替えます

$ docker context create --docker "host=ssh://hexa@192.168.1.2" main
main
Successfully created context "main"
$ docker context use main
main
Current context is now "main"

接続してコンテナを作成できるか確認してみます

$ docker container run --rm -it centos:7 bash -c 'cat /etc/redhat-release'
CentOS Linux release 7.7.1908 (Core)

接続できました

コンテキストは複数作成しておけますし、環境に合わせて簡単に切り替えが可能なため、覚えておくととても便利です

$ docker context use sub
sub
Current context is now "sub"
$ docker context ls
dNAME                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
main                                                          ssh://hexa@192.168.1.2
sub *                                                         ssh://ubuntu@192.168.2.2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nginx-proxy と docker-compose でレガシーなLAMP(LEMP)環境を複数稼働できるようにした

この記事は、Makuake Development Team Advent Calendar 2019 7日めの記事です。

yutakiと申します。
PRIME ORDERというWebシステム開発サービスのエンジニアリングマネージャをやっております。
https://prime-order.jp/

概要

最近、レガシーな案件のプレビューのために
十数年ずっと頑張ってきたオンプレの共有開発サーバが故障してしまい
急いでnginx-proxyとdocker-composeで複数案件の開発環境を復活させた話をします。

やったこと(ざっくり)

※端末はmacです
基本的に下記のコンテナ構成でプロジェクトを作成

  • 新しめ(laravel系): [nginx] + [php] + [mysql] + [node(build専用)] の4台構成
  • 古め(その他): [apache & php] + [mysql] の2台構成

そしてnginx-proxyでローカルドメインを割り当てました。
たとえばhogeプロジェクトであれば http://hoge.localhostで使えるようにした感じです。

1. 案件ごとの構成をdocker-composeで作成

ベースimageの選定について

一般的な構成の時は、app以外はほぼ公式イメージで対応しました。

appについては、docker-php-ext-installを使えるので、エクステンションの管理も楽です。
一部、pecl経由でないと入れられないものもありますがそれはpeclで入れて有効にすればいいだけ。
(imagemagickとか)

FROM php:7.1-fpm-alpine

# lib
RUN apk add --no-cache --virtual build-dependencies gcc make autoconf libc-dev libtool \
 && apk add --no-cache --virtual zlib1g-dev libxml2-dev \
 && apk add --no-cache --virtual libmagickwand-dev libpng-dev imagemagick-dev

# composer
COPY --from=composer /usr/bin/composer /usr/bin/composer

# php extension
RUN docker-php-ext-install zip xml pdo_mysql gd

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

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

COPY php.ini /usr/local/etc/php/

WORKDIR /var/www

こんな感じで書いたものを使いまわしていけました。


apacheが一緒になっている5.4系のイメージなどはこんな感じ。

FROM php:5.4.45-apache

# composer
COPY --from=composer /usr/bin/composer /usr/bin/composer

# lib
RUN apt-get update \
  && apt-get install -y git libmagickwand-dev libmcrypt-dev

# php extension
RUN docker-php-ext-install mcrypt pdo pdo_mysql zip \
  && pecl install imagick \
  && docker-php-ext-enable imagick

# apache module
RUN mv /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled/ \
  && mv /etc/apache2/mods-available/headers.load /etc/apache2/mods-enabled/

# conf, ini
COPY site.conf /etc/apache2/sites-enabled/site.conf
COPY php.ini /usr/local/etc/php/php.ini

WORKDIR /var/www/site

apacheのモジュールがconfファイルを移動するだけで有効になるので楽なのがポイントです。


あとは、ごく少数、PHP5.2みたいな古いバージョンが使われている社内システムもありました。
そちらは公式にはバージョンがないので、DockerHubで該当バージョンを使っているものを探して利用しました。

例えば下記など。
https://github.com/andres-ortiz/php5.2-apache2.2
セキュリティパッチ等当ててくれているので開発環境とはいえありがたいです。
ただこのimageは起動時にrun.shでhtdocs以下にログディレクトリを作成してしまうので
スクリプトを上書きするかDocumentrootを変更するなどすると良いと思います。

2. nginx-proxy

こちらを利用します。
https://github.com/jwilder/nginx-proxy

使い方の要点は下記です。

  • どこでもいいのでnginx-proxyをdocker-compose.yml経由で起動しておく
  • 各案件のdocker-composeの調整
    • 「VIRTUAL_HOST」という環境変数でホスト名を定義する
    • nginx-proxyと同じネットワークに所属させる

nginx-proxyを起動しっぱなしにしておけば特に他に設定が不要という神ツールです。
(dockerのプロセスを監視、各docker-composeのup、downを検知して勝手にプロキシしてくれます)

nginx-proxy本体のdocker-compose.yml

docker-compose.ymlは最小構成ならこれだけ

docker-compose.yml
version: '2'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always

ちなみにネットワーク名は固定してもいいと思います。
nginx-proxyというディレクトリで起動しているなら「nginx-proxy_default」
がネットワーク名になります。

各案件のdocker-compose.yml

たとえばこう

docker-compose.yml
version: '3'

services:
  hoge-web-php:
    image: ${WEB_PHP_IMAGE}
    build: docker/web-php
    ports:
    - 80
    volumes:
      - ./logs/apache2:/usr/local/apache2/logs:cached
      - ./server:/usr/local/apache2/app:cached
    environment:
      VIRTUAL_HOST: hoge.localhost
  hoge-db:
    image: ${MYSQL_IMAGE}
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DBNAME}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${MYSQL_TZ}
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./logs/mysql:/var/log/mysql:cached
    - ./docker/db/data:/var/lib/mysql:cached
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 3306
# 下記はnginx-proxyを利用するときのみ解放
networks:
  default:
    external:
      name: nginx-proxy_default

webサーバのコンテナのポートは80番にしてください。
あと、サービス名(hoge-web-php, hoge-dbなど)はネットワーク内でuniqueである必要があります。
案件名をprefixなどでつけておくといいと思います。

ポイントは、hoge-web-phpの

docker-compose.yml
environment:
  VIRTUAL_HOST=hoge.localost

そして最下部の

docker-compose.yml
networks:
  default:
    external:
      name: nginx-proxy_default

です。
なお、このnetworksセクションをコメントアウトしてしまえば通常通りポートフォワードでの利用もできます。
nginx-proxyにproxyさせたいときだけ記述すれば良いです。

これで、http://hoge.localhostで該当案件を処理することができます。

その他

proxyの設定をいじりたい場合

confを上書きすれば簡単です。

こんなふうに上書き

docker-compose.yml
    volumes:
      - ./proxy.conf:/etc/nginx/proxy.conf

デフォルトの中身はこうです。
(必要なものは揃っている印象です)

proxy.conf
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;

# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";

SSL対応(リンク紹介だけ)

nginx-proxyをssl対応する場合は
https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion
を活用すれば死ぬほど簡単に対応できます。

双方の活用は、素晴らしい参考記事がありましたので貼っておきます
https://tech.quartetcom.co.jp/2017/04/11/multiple-ssl-apps-on-one-docker-host/

Laravel Valetを利用する場合

80/443ポートの食い合いになるので使う時はvalet stopしてください。
Laravel Valetはローカルにnginxを立ち上げていて、dnsmasqと併用して似たようなことをしています。

valetと共存させようと欲張り、nginx-proxyのポートを8080:80
にして運用しようとしてみたところ、プロキシはされるのですが
ローカル -> nginxの間のX-Forwarded-*系を伝搬させることができず
Laravelのrouteメソッドなどで適切なURLが作成できませんでした。
しばらく調べてみたんですが、案件ごとにごちゃごちゃ設定するしか手がなさそうだったので断念。

これTCPのプロキシならMySQLもいけるんじゃ?->いけなかった

MySQLも3306:3306でプロキシしてhoge-db.localhostでSequel Proとかでアクセスできるんじゃないのか?
と思ってやってみましたが、ホストは解決できるものの正常に通信はできませんでした。
超残念。こちらは大人しくサービスごとにport設定するなどしています。
issueでも似たこと話してました。
https://github.com/jwilder/nginx-proxy/issues/318

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

【画像で説明】KaggleのNoteBookでpickleをつかい回す

やること

  1. csvファイルのデータセットをpickle化
  2. pickleファイルを別のnotebookで読み込む

pickleってなんなのよ?

pythonオブジェクトをバイナリデータで保存するやつ
https://docs.python.org/ja/3/library/pickle.html

なにがうれしくて?

読み込みが早い
バイナリデータなのでパーズ処理がいらんから早いんですって
学習済みモデルとかもpickle化してつかい回せる

こちらの検証記事がすばらしいです
Python: pandas の永続化フォーマットについて調べた

タイタニックのデータでやってみる

ひとまずtrain.csvをpickleにする
コードはこれだけ

# pickleは標準ライブラリなのでinstall不要
import pickle

import pandas as pd


train = pd.read_csv('../input/titanic/train.csv')

# 'wb'(write binary)を指定
with open('train.pickle', 'wb') as f:
    pickle.dump(train, f)

Datasetとして保存

まずはcommit
スクリーンショット 2019-12-09 3.16.06.png

左上に緑のCompleteがでたらOpen Versionをポチ
スクリーンショット 2019-12-09 3.17.42.png

Outputの欄までスクロールして
スクリーンショット 2019-12-09 3.18.37.png

train.pickleが確認できたらNew Dataset
スクリーンショット 2019-12-09 3.19.07.png

Datasetのタイトルを入力してCreateすると
スクリーンショット 2019-12-09 3.20.36.png

Datasetが完成
スクリーンショット 2019-12-09 3.21.08.png

別のnotebookにもってくる

新しいnotebookをつくったら+ Add Data
スクリーンショット 2019-12-09 3.44.17.png

Your Datasetsでフィルタリングして
スクリーンショット 2019-12-09 3.44.44.png

さっきつくったやつをAdd
スクリーンショット 2019-12-09 3.45.11.png

ここに表示されてれば優勝
スクリーンショット 2019-12-09 3.46.31.png

読み込んでみよう

コードはこれだけ

# 'rb'(read binary)を指定
with open('../input/titanicdatasetpickles/train.pickle', 'rb') as f:
    train = pickle.load(f)
train.shape

# (891, 12)



ディレクトリ名は画面右に表示されてるものと違うことがあるのでお気をつけください
https://www.kaggle.com/anata-no-namae/data-set-no-namae <- この表記がディレクトリ名になる

!ls ../input

# titanicdatasetpickles

ファイル化してもよいかも

dumpの処理は使いまわせそう

dump_pickles.py
import pickle

import pandas as pd


# Kaggle上と別環境でpathを切り替え
if '/kaggle/working' in _dh:
    input_path = '../input'
else:
    input_path = './input'

# コンペごとにココだけ書き換える
data_sets = {
    'train': f'{input_path}/titanic/train.csv',
    'test': f'{input_path}/titanic/test.csv',
    'gender_submission': f'{input_path}/titanic/gender_submission.csv'
}

for name, path in data_sets.items():
    df = pd.read_csv(path)
    with open(f'{name}.pickle', 'wb') as f:
        pickle.dump(df, f)

pandasでも同じことできる

# これは
with open('./train.pickle', 'wb') as f:
    pickle.dump(train, f)

# こう
train.to_pickle('./train.pickle')
# これは
with open('../input/titanicdatasetpickles/train.pickle', 'rb') as f:
    df_ss = pickle.load(f)

# こう
train = pd.read_pickle('../input/titanicdatasetpickles/train.pickle')

こんなエラーでるときある

ModuleNotFoundError: No module named 'pandas.core.internals.managers'; 'pandas.core.internals' is not a package

とか言われたらpandasのバージョンの問題らしい

# Kaggleのnotebookのpandasバージョンとあわせるとよいかも
pip install -U pandas==0.25.3

で解決
※ Kaggleの公式dockerイメージ(kaggle/python)はpandas==0.23.4でエラーが出たので注意(2019/12/09現在)

こちらの記事に救っていただきました
pickleとpandasの不整合

おしまい

最後まで読んでいただいてありがとうございました

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

dockerで立てたMySQLコンテナに外部から接続できずにハマった時の話

概要

dockerを触り始めてMySQLのコンテナを立ててみたものの、Macのターミナルから接続できずに結構ハマった話。似たような事象に陥ってる人が、もしこれで解消できる人がいればと思い記事にしました。

結論

コンテナの3306ポートをEXPOSEしていなかった。docker runで-Pオプションを付けて実行すると上手く接続できた。

before
$ docker run -d --name test-db -e MYSQL_ROOT_PASSWORD=password mysql/mysql-server
after
$ docker run -d --name test-db -e MYSQL_ROOT_PASSWORD=password -P --expose=3306 mysql/mysql-server

経緯とか

症状

作成したdockerコンテナに対して、Macのターミナルからmysqlコマンドで入ろうとしても、以下の通りエラー

$ mysql -h 127.0.0.1 -u user -p
Enter password: 
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (61)

エラー内容的にそもそもIPアドレスがわからないと言われている。
ネットで上手く接続できている人たちの記事を見ると、docker ps時のPORTSに0.0.0.0があることに気がつく。というわけで、以下の記事を参考にEXPOSEしてみた。

MySQLに外部接続できない時のチェックポイント

EXPOSEする前
$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                       PORTS                                               NAMES
35b706562745        mysql/mysql-server   "/entrypoint.sh mysq…"   About an hour ago   Up About an hour (healthy)   3306/tcp, 33060/tcp                                 test-db
EXPOSEした後
$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                       PORTS                                               NAMES
42a9d040091a        mysql/mysql-server   "/entrypoint.sh mysq…"   14 minutes ago      Up 14 minutes (healthy)      0.0.0.0:3306->3306/tcp, 0.0.0.0:32772->33060/tcp    test-db

EXPOSE後のPORTS欄を見ると0.0.0.0:3306->3306/tcpとなっており、ローカルホストから接続できていることがわかる。

コピペで適当に作業していると何かあったときにハマりますね...

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

GCSをマウントしたWeb Server Containerをcreate-with-container でサックリ動かす - Cloud shell で 開発環境要らず

要約

  1. Chromeのみで
  2. GCP Console 付属の Cloud Shell を駆使し
  3. Cloud Storage をマウントした Docker Container を
  4. gcloud compute instances create-with-container コマンドで
  5. サックリ簡単にWebサーバを立ち上げます
  6. Croud Run は gVisor が強力にコンテナ外へのアクセスを制限しているのでgcsfuseが使えないよ

動機

あっ、Webサーバ立てなきゃ、、という時に、IDEやgcloudコマンドやDockerコマンド、その他諸々開発ツールがインストールされたマシンが手元にないケースって割とありますよね。急いでるのに、諸々コマンドやら開発ツールなんか入れてる時間なんて無い…
そんな時でも、ChromeがインストールされたWindows,MAC,iPhone,iPad,Android,Chromebookの何れかがあれば、30分くらいでサックリ目的を達成することができます。

手順

Chromeが動くなにかを用意する

スマートフォンでできなくも無いですが、できれば少し大きめな画面とキーボードは欲しいです。

GCPコンソールを起動する

https://console.cloud.google.com/ からGCPコンソールを起動します。
GCPをまだ使ったこと無い、アカウントが無い、という方は、この辺の記事の頭の方を参考にしてGCPコンソールを使えるようにしてください。

Google Cloud Shell を起動する

Cloud Shell とは

Google Cloud Shell は、Google Cloud Platform でホストされているリソースを管理するためのシェル環境です。無料で週80時間まで使えます。gcloud コマンドや、go コンパイラ、git, dockerコマンドなど、開発に必要なものは一通り揃ってて便利です。
詳しくはこちら⇒https://cloud.google.com/shell/docs/?hl=ja

起動方法

GCPコンソール右上のCloud Shell 起動アイコンをクリックします。
image.png
ブラウザ画面下部にShell画面が立ち上がります。

Cloud Shell を初期化する

Cloud Shell 環境に、ターゲットのGCP ProjectID を設定します。

gcloud config set project [project id]

Google Cloud Strage のバケットを作る

GCS bucketを作ります。
cloud shell内で以下のコマンドを叩いてください。
[bucketname-for-you] の部分は適当な名前を入れてください。名前は全世界で一意で早いものがちです。testとかfoobarとかは誰かがきっととってます。

gsutil mb -l asia-northeast1 -b on gs://[bucketname-for-you]

エディタを立ち上げる

Cloud Shell 画面右側にある鉛筆アイコンをクリックしてエディタを立ち上げます。
image.png
すると、今までダッシュボードが表示されていたブラウザ上部がエディタ画面に切り替わります。

Dockerfile他色々書く

エディタ部分の左側ペインにフォルダ構造が表示されています。
適当なフォルダ(ここではwebserverとします)を作り、作ったフォルダのサブフォルダにhtmlフォルダを作ります。
作ったフォルダ直下にDockerfile,php.ini,entrypoint.shを置きます。
htmlフォルダ下にindex.phpを置きます。

?webserver
 ├─?html
 │ └─?index.php
 ├─?Dockerfile
 ├─?php.ini
 └─?entrypoint.sh

今回のコンテナは、以下の構成とします。

  • apache
  • php7
  • gcsfuse
  • gosu
Dockerfile
FROM php:7-apache

ENV GCSFUSE_REPO gcsfuse-jessie

RUN apt-get update && apt-get install --yes --no-install-recommends \
    && apt-get install --yes gnupg \
    ca-certificates \
    curl \
  && echo "deb http://packages.cloud.google.com/apt $GCSFUSE_REPO main" \
    | tee /etc/apt/sources.list.d/gcsfuse.list \
  && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
  && apt-get update \
  && apt-get install --yes gcsfuse \
  && apt-get install --yes gosu \
  && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && mkdir /data

COPY php.ini $PHP_INI_DIR
COPY ./html /var/www/html
COPY entrypoint.sh /usr/local/bin/entrypoint.sh

RUN chown -R www-data:www-data /var/www/html \
 && chown -R www-data:www-data /data \
 && chmod a+x /usr/local/bin/entrypoint.sh \
 && sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf

#EXPOSE 80 443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
index.php
<?php 
phpinfo();
?>
php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

gcsfuseは、マウントしたフォルダを使うユーザーで実行しないとフォルダにアクセスできません。ので、gosuでユーザーを指定して実行します。
Dockerfile内でUSER www-data でもマウントできますが、そうするとapacheが80番ポートをListenできなくなります。

entrypoint.sh
#!/bin/bash
gosu www-data gcsfuse $GCS_PATH /data
source /etc/apache2/envvars
exec apache2 -D FOREGROUND

Dockerをビルドし、立ち上げる

Cloud Shell でビルドします。
カレントフォルダを./webserver に移動し、ビルドします。

docker build -t web-container .

cloud shell内でコンテナを起動します。cloud shell 自体もDocker Container なのですが、その中で更にテストの為にContainerを起動します。

docker run -e PORT=8080 -e GCS_PATH=[bucketname-for-you] \
  --device /data --privileged -p 8080:8080 -d \
  --name php-web-container web-container

結果を見る

エディタ画面右上の枠の中に菱形が描かれたよくわからないアイコンをクリックすると、 ポート8080でプレビュー というメニューが出てくるので選択します。
image.png
新しいタブで動作確認しましょう。

Google Container Registory に登録する

駆け足で

docker build -t gcr.io/$(gcloud config get-value project)/web-container .
docker push gcr.io/$(gcloud config get-value project)/web-container

インスタンスを作る

gcloud compute instances create-with-container web-container-instance \
  --container-image gcr.io/$(gcloud config get-value project)/web-container \
  --container-privileged \
  --container-env PORT=80,GCS_PATH=[bucketname-for-you] \
  --tags http-server \
  --zone=asia-northeast1-a \
  --scopes=default,storage-full

インスタンスができて80番ポートでListen開始です。

まとめ

これでChromeブラウザさえあれば、いつでもどこでもWebサーバが立てられます。
1コンテナ1プロセスとか、マウントしたGCSバケット使ってないよね、とか、そういった事は適宜調整してくれれば、と思います。
SSL/TLSはCloud Loadbalancerを使いましょう。無料のSSL証明書がいい感じに使えます。
Static なHTMLだけのWebサイトなら、GCSバケットを直接Cloud Loadbalancerから参照すれば、インスタンス分のお金がかからずいい感じです。

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

.NETCoreのWebAPIをDockerで実行する

はじめに

昔使ってた.NETCoreを使って、WebAPIでDockerを動かしてみたいと思いました。ちなみに私、.NET環境触るの久しぶりだったり、Docker初心者なので、間違ってる〜とか今はこんなのが主流だよ〜とかあったらご指摘いただけると嬉しいです:relaxed:

また、Docker良く分からないけど、なんか動かしてみたい〜っていうMSフレンズも実際にやってもらえたら嬉しいです。

TL;DR

  • C#やWindowsServerを使っている方、.NETCoreやDockerを検討している方向けです。
  • この記事を読むと、.NETCoreのWebAPIをDockerコンテナで実行できるようになります。

準備

  • 環境
    • macOS Mojave バージョン 10.14.6
      ※Windows でも適宜読み替えればできると思います。

.NETCoreをインストール

.NETCore SDKからダウンロードします。

image.png

image.png

バージョンを確認すると...

dotnet --version
--version3.1.100

正常に.NETCore SDKのインストールが完了しました。

WebAPIのサンプルプロジェクトを作成

続いて、今回試すAPIのプロジェクトを作成します。

# プロジェクト作成 (my_docker_api は任意のプロジェクト名)
dotnet new webapi -o my_docker_api

動作確認

# プロジェクトに移動してlocalhost起動
cd my_docker_api/
dotnet run

サンプルとして用意されているWeatherForecast
https://localhost:5001/WeatherForecast でアクセスすると...

image.png

Command + C でlocalhostシャットダウン

Dockerイメージをビルド

準備

※Dockerをインストール(この記事では割愛)

docker --version
Docker version 19.03.5, build 633a0ea

VSCode拡張機能インストール(任意)

image.png

Dockerfile作成

Command + Shift + P でコマンドパレットを開き
Docker: Add Docker Files to Workspace... を選択
ASP.NET Core > Linux を選択すると自動で Dockerfile が作成されます。
image.png

Dockerイメージをビルド

my_docker_api_image は任意のイメージ名でOKです。

docker build -t my_docker_api_image -f Dockerfile .

数分かかりますが、最終的に Successfully が出ていればOKです。
image.png

Dockerイメージの確認

docker images

4つのイメージが作成されていることがわかります。
image.png

コンテナーを実行する

docker run --rm -p 5000:80 my_docker_api_image my_docker_api

--rm: このコンテナー実行後に自動的にコンテナを削除してくれます。不要なコンテナが残り続けるとストレージを圧迫します。
-p 5000:80: ローカル端末の5000番ポートをDockerコンテナー内の80番ポートに接続します。

実行後にhttp://localhost:5000/WeatherForecastにアクセスすると、Docker内のAPIを実行できることを確認できました。

image.png

先程、dotnet run でローカル実行したときと同様のことがDockerコンテナで実行できることが分かります。

感想

これだけではDockerの旨味は感じられにくいが、Dockerで開発することができれば、そのままAWS ECS/Fargateを使ってデプロイできるみたい。(でも今は、.NETCore2系まで対応で3系はまだかな?) マイクロサービスやサーバーレスアプリケーションを作成するなら使いたい技術だと感じました。

〜後日談〜
Dockerには様々な使い方があります。例えば、開発環境をメンバー全員で揃えたいとか、サービス運用を楽にしたいとか。つまり、解決したい課題を明確にして自分がどのようにDockerを使いたいか把握することが大事なんだと気づきました。

参考にした記事

私たちのチームで働きませんか?

alt
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!

上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!

Qiita Jobsよりメッセージお待ちしております!

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

OS別、DockerクライアントのみをインストールしてリモートのDockerに接続する為の設定方法

DockerデーモンにTLS接続出来るのが前提の記事となっています。

検証環境

  • Windows 10 Pro version 1909
  • Ubuntu 19.04
  • macOS Catalina

共通の手順

クライアントのインストール手順はOS毎に違いますが、接続先の設定は認証用のファイルを~/.dockerに置いて環境変数を設定するだけです。

公式手順に従っていた場合はca.pem,cert.pem,key.pemの3つのファイルが出来ているはずなので、この3つのファイルを各クライアントにコピーし、~/.dockerに移動しておきます。

デフォルトの接続先をUNIXソケットからTCPソケットに変えるには下記の2つの環境変数を設定します。

DOCKER_HOST=tcp://hoge.exampl.com:2376
DOCKER_TLS_VERIFY=1

上記のhoge.exampl.com:2376は接続先のFQDNとポートに置き換えてください。

Docker Clientのインストール (Ubuntu)

公式手順の最後のsudo apt-get install docker-ce docker-ce-cli containerd.ioのところでdocker-ceとcontainerd.ioを省くだけです。

Docker Clientのインストール (macOS)

Homebrewがdockerを配布しているので、これを利用するのが簡単です。多分クライアントだけだと思います。

brew install docker

Docker Clientのインストール (Windows)

Windowsでは決定版的パッケージマネージャーが存在せず、少なくともMSYS2のpacman等ではdockerの配布はされていません。

取れる手段は3つです。

  1. WSLにインストールして、Windows側にwsl上のdockerを呼び出すラッパーを用意する。
  2. 公式配布の古いバイナリをダウンロードし、パスを通す。
  3. 自分で公式ソースをビルドし、パスを通す。

1番はお手軽ではあるものの、VSCodeのRemote - Containersが使う||の解釈に失敗して上手く動きませんでした。

2番もお手軽ではあるものの、VSCodeのRemote - Containersが未実装のオプションを利用する為上手く動きませんでした。

よってここでは3番で行きます。

Docker Clientをビルドする

gitコマンドが使える、dockerがインストールされている環境で作業します。
dockerはクロスビルドに対応している為、Windowsでなくて構いません。

適当なディレクトリを作り、そこにdocker-ceのリポジトリをcloneします。

$ mkdir ~/docker-ce 
$ cd ~/docker-ce
$ git clone https://github.com/docker/docker-ce.git .

これで取得するソースは開発版になるので、安定版が良い場合はバージョンをタグで指定してcheckoutします。

v19.03.5への切り替え
$ git checkout refs/tags/v19.03.5

次にDocker Clientのディレクトリに移動し、ビルドを行います。

$ cd components/cli/
$ make -f docker.Makefile binary-windows

makeにdockerコマンドを利用している為、dockerコマンドにsudoが必要な環境ではmakeの前にsudoをつけてください。

ビルドが完了するとbuildフォルダの中にdocker-windows-amd64というファイルが出来ているので、これをWindowsの適当なディレクトリにscp等でコピーします。

コピーしたファイルをdocker.exeにリネームし、環境変数のPATHにdocker.exeのあるディレクトリへの絶対パスを追記すればインストール完了です。

参考文献

Get Docker Engine - Community for Ubuntu

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

Ubuntu 19.04でDockerデーモンにTLSで接続できるようにする

対象とする環境

Dockerのインストールが済んでいる、Ubuntu 19.04を想定しています。

Ubuntu 18.04 LTS以降であれば、systemdを使っているので同じ手順で大丈夫だとは思います。

手順

公式ドキュメントに沿って進めます。

実行にroot権限が必要なコマンドは文頭に#を、そうでなければ\$をつけています。

複数のファイルを生成するので、適当なディレクトリを作成して移動しておきます。
ここではホームディレクトリにdocker-authというディレクトリを作ることにします。

$ mkdir ~/docker-auth && cd ~/docker-auth

自己署名用の秘密鍵を作成する

$ openssl genrsa -aes256 -out ca-key.pem 4096

パスフレーズを求められます。このパスフレーズはDockerのTLS接続時に使うものではないので、長くて複雑なものを用いて構いません。

自己署名用の証明書要求を作成する

$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem

daysで設定する整数値は証明書要求ファイルの有効日数です。ここでは1年間にしています。必要に応じて調整してください。

パスフレーズの入力を求められるので、先ほど秘密鍵で設定したパスフレーズを入力してください。

その後所在地の入力等を求められるので適当に入力します。Common NameにはDockerソケットの公開元であるサーバーのFQDNを入れてください。

サーバー用の秘密鍵を作成する

$ openssl genrsa -out server-key.pem 4096

サーバー側の処理であり、パスフレーズの入力ができない為、こちらはパスフレーズを設定しません。

サーバー用の証明書要求を作成する

$ openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr

\$HOSTはサーバーのFQDNと置き換えてください。

サーバー用の自己署名証明書を作成する

$ echo subjectAltName = DNS:$HOST,IP:$HOST_IP,IP:127.0.0.1 >> extfile.cnf
$ echo extendedKeyUsage = serverAuth >> extfile.cnf
$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-cert.pem -extfile extfile.cnf

\$HOSTはこちらでもサーバーのFQDNと置き換えます。

\$HOST_IPはサーバーへの接続に使うIPアドレスを設定します。自分はサーバーを自宅に置いている為、LAN内からでも接続できるようにプライベートIPを指定しました。

こちらも有効期間は1年間としています。必要に応じて調整してください。

パスフレーズには最初に設定したものを入力してください。

クライアント用の秘密鍵を作成する

$ openssl genrsa -out key.pem 4096

こちらも利便性の為にパスフレーズを設定しません。

クライアント用の自己署名証明書を作成する

$ echo extendedKeyUsage = clientAuth > extfile-client.cnf
$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out cert.pem -extfile extfile-client.cnf

こちらも有効期間は1年間としています。必要に応じて調整してください。

パスフレーズには最初に設定したものを入力してください。

不要なファイルを削除し、権限を設定する。

$ rm -v client.csr server.csr extfile.cnf extfile-client.cnf
$ chmod -v 0400 ca-key.pem key.pem server-key.pem
$ chmod -v 0444 ca.pem server-cert.pem cert.pem

証明書要求ファイルと拡張設定ファイルは証明書の作成が済めば不要なのでここで削除しておきます。

残ったファイルはどれも大切に保管する必要があるので、適切な権限を設定しておきます。

DockerデーモンへのTLS接続を許可する

# mkdir /etc/docker/auth-files
# cp {ca,server-cert,server-key}.pem /etc/docker/auth-files

Dockerデーモンが用いる証明書要求、サーバー用自己署名証明書、サーバー用秘密鍵を適当な位置に移動しておきます。

Ubuntu19.04ではデーモンの起動にsystemdを用いているため、/lib/systemd/system/docker.serviceにある設定ファイルを編集します。

docker.service
~省略~
[Service]
~省略~
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/etc/docker/auth/ca.pem --tlscert=/etc/docker/auth/server-cert.pem --tlskey=/etc/docker/auth/server-key.pem -H tcp://0.0.0.0:2376 -H fd:// --containerd=/run/containerd/containerd.sock
~省略~

dockerdの起動オプションに--tlsverify --tlscacert=/etc/docker/auth/ca.pem --tlscert=/etc/docker/auth/server-cert.pem --tlskey=/etc/docker/auth/server-key.pem -H tcp://0.0.0.0:2376を追記しました。

ここで指定するTLS接続の為の設定はあくまでtcp接続の際に用いるものなので、-H fd://を残しておくことでサーバー上ではこれまで通りUNIXソケットを経由してdockerコマンドを使うことができます。

あとはDockerデーモンを再起動すればTLS接続が有効になっています。

ufw等を使っている場合は適宜ポート開放を行い、クライアントからdockerコマンドで接続出来ることを確認してください。

OS別のDockerクライアントインストール方法は別記事にて取り扱います。

参考文献

Protect the Docker daemon socket

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

VSCodeとリモートDockerを利用した、シンクライアントな開発環境の構築

この記事はSFC-RG Advent Calendar 2019の11日目です。

Docker使っていますか?

Docker便利ですよね。仮想環境より遥かに低コストで、プロジェクト単位で開発環境を用意できるので、ホスト環境が次第に汚れていく不快感から永遠にオサラバすることができます。

一方、DockerデーモンはLinuxでしか動かない為、macやWindowsでDockerを使う場合はDockerデーモンを動かす為に仮想環境が必要となり、意外とリソースを消費してしまいます。

Visual Studio Code(以降VSCode)では、拡張機能の一つであるRemote Developmentを使うことで、コンテナを開発環境として利用することが可能です。

これにはdockerコマンドを用いているため、Docker Remote APIを使ってリモートのコンテナも開発環境として利用することが出来ます。

Dockerをサーバーで動かす

  • メリット
    • 非力なノートパソコンでもDockerを快適に利用可能
    • クライアントが変わっても作業を継続可能
    • 自宅で暇しているサーバーに仕事を与えられる
    • 話題のシンクライアント
  • デメリット
    • 最初の準備がやや大変
    • オフラインでは作業不可
    • 自宅にサーバーが無い場合は別途用意する必要がある

検証に用いた環境

  • サーバー
    • Ubuntu 19.04
      • Docker Engine - Community version 19.03.4
  • クライアント
    • Windows 10 Pro version 1909
      • Docker Client version 19.09.0-dev (自分でソースからビルド)
    • macOS Catalina version 10.15.1

他ソフトウェア、拡張機能は2019年12月時点での最新安定板を利用

必要なもの

サーバー側

  • Docker

クライアント側

  • Docker client
  • Visual Studio Code

サーバーでの準備

サーバーでDockerデーモンを動かし、これをDocker Remote APIでクライアントから扱うには、Dockerデーモンのソケットをネットワークに露出させる必要があります。

Dockerデーモンへのアクセス権はroot権限を意味する為、セキュリティの観点からTLSを用いた認証、暗号化が必須です。

別記事を用意したのでそちらに従うか、公式ドキュメントを参考にDockerデーモンにTLSを使ったTCP接続が出来るようにしてください。

クライアントでの準備

dockerコマンドのインストールと設定

クライアントにはDockerデーモンを入れる必要はありませんが、Dockerクライアントはインストールし、接続先を指定する必要があります。

UbuntuやmacOSではパッケージマネージャーを用いてDockerクライアントのみのインストールが可能です。

Windowsの場合は公式からの安定版のバイナリ配布が17.09.0で止まっており、これはVSCodeでは動かない為、自分で公式ソースからバイナリをビルドして適当な場所に配置し、パスを通す必要があります。

詳細手順はこちらも別記事を用意したのでこちらに従ってください。

Visual Studio Codeのインストールと設定

リモートコンテナへの接続はVSCode公式でも言及がなされています。

VSCodeをインストールした後、Remote Development拡張機能を追加します。
また、Docker拡張機能も、リモートのコンテナを直接管理出来て便利なので追加します。

Docker拡張機能はそれ自体がDocker Remote APIを利用するため、VSCodeの設定ファイルに下記1行を追記してください。

settings.json
{
    "~省略~",
    "docker.certPath": "~Dockerデーモンへの接続に必要な認証ファイルを置いたディレクトリへの絶対パス~"
}

VSCodeのDockerタブからリモートのコンテナやイメージ等が見れるようになれば準備完了です。

使用例

以降クライアントでVSCodeのみを操作します。サーバーでの操作は一切必要ありません。

作業ディレクトリを開く

適当な場所に作業ディレクトリを作って開きます。ディレクトリ名はDockerイメージ名に使われるので、プロジェクト名にでもしておくと便利です。

開発コンテナ設定ファイルを作る

VSCodeではdevcontainer.jsonという独自のファイルで開発環境とするコンテナの設定を記述します。

公式で既に非常に多くのテンプレートが用意されているので、これにリモート用の設定を追記するのが簡単です。

ウィンドウ左下の緑のボタンからRemote-Containers: Add Development Container Configuration Files...を選ぶことでdevcontainer.jsonが生成されるので、これに下記2行を追記します。

devcontainer.json
{
    "~省略~",
    "workspaceMount": "src=remote-workspace,dst=/workspace,type=volume,volume-driver=local",
    "workspaceFolder": "/workspace"
}

上記の設定文中のremote-workspaceはプロジェクトファイルを保存することになるボリューム名ですので、プロジェクト名に置き換えるなど、一意性を保てるように適宜変更してください。

コンテナは永続性の必要なデータを置く場所ではない1ので、上記設定を追加することでボリュームに保存するようにします。

勿論ボリュームも通常のディレクトリと同じく、絶対的な永続性を保証する機能ではない事には注意してください。

開発コンテナに接続する

設定ファイルが用意出来たら後は接続するだけです。

ウィンドウ左下の緑のボタンからRemote-Containers: Reopen in Containerを選べば、VSCodeが設定ファイルを元にコンテナの作成、実行、接続を自動で行ってくれます。

初回実行時はイメージのビルドも行うため暫く時間がかかりますが、2回目以降はより高速に接続が完了します。

接続後はローカルでの開発と変わらない使い勝手でリモート上にプロジェクトファイルを展開していくことができます。

他のクライアントから接続する

設定ファイルのある作業ディレクトリを丸ごとコピーしてきて、Remote-Containers: Open Folder in Container...を実行するだけです。

参考文献

DockerデーモンへTCP接続をする方法
Protect the Docker daemon socket
VSCodeからリモートのコンテナを利用する方法
Developing inside a container on a remote Docker host


  1. コンテナとボリュームの関係についての概要はこちらの記事がわかりやすいです。この記事で作るボリュームはDockerの管理するボリュームの事です。 

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

オレオレ開発環境2017〜2019年版

この記事はひとり開発 アドベントカレンダー 2019年の8日目の記事です。
開発環境の紹介 (VM->Docker with tmux, vim)と題して、ここ3年に渡る私のターミナル周りの開発環境の推移をまとめていく記事です。

2017年 dotfiles + git

github - u1and0/dotfiles

dotfilesとはなんぞやについては今年のアドベントカレンダー1発目に詳しく書かれているので参照してみてください。
ようこそdotfilesの世界へ

インストール方法

$ bash -c "$(curl -fsSL git.io/dotu1)"

ワンコマンドでインストールできます。ただし、インストール時に表示される注意書きにもあるように、インストール先のホームディレクトリ上のファイルを上書きするので、上書きされたくないファイルがあったら予めリネームするなどして退避してください。あくまで自分用にしか作っていないので、インストールするときはファイルを上書きするし、アンインストール方法は用意されていません。ファイルが損失しても責任を取りかねます。

ジョブ内容

  • dotfilesをクローンしてファイルを上書きします。
  • bacpacを使ってArchlinuxで使っていたのパッケージをインストールします。
  • pyenvを使用してpython 環境を構築します -> 今はdockerでpython環境を構築しているので基本的に使いません。
  • デフォルトのshellをzshに変更します。

bacpac

元はこれAUR - bacpac
フォークしたのが自分のGist Gist - u1and0/.bacpac.md Secret

パッケージマネージャにpacmanを使っているOSなら使えます。
インストールしたパッケージをgitで管理するshell script。古いのかもしれないです。
元のソースと比べてbacpac cat, bacpac diffのサブコマンドと、zshの補完機能_bacpacを追加しています。

submoduleで管理しています。

.gitmodules
[submodule "bacpac"]
    path = bacpac
    url = https://gist.github.com/u1and0/8bd32ade8d95988b52b03a1b08297b96
    ignore = dirty  # dotfilesには関係ないパッケージの追加・削除による差分を感知しない

zsh

  • .bash_aliases
  • .bash_functions
  • .bashrc
  • .zsh_aliases
  • .zsh_functions
  • .zsh_keybinds
  • .zshrc
  • .zplug.zsh

この辺で管理しています。
.zshrc呼び出すときに.bashrcをsourceしているので、bashの設定をオーバーライドしています。

.zshrc
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi
if [ -f ~/.zsh_aliases ]; then
    . ~/.zsh_aliases
fi
if [ -f ~/.zsh_functions ]; then
    . ~/.zsh_functions
fi

zplug

zplugはzshに入れるプラグインマネージャです。
以下の設定を.zshrcに書いておくことでzplugをgithubからクローンしてzplug及びzplugで管理されるプラグインをインストールしてくれます。

.zshrc
# 2回目以降は.zplug/init.zshを読み込んでロード
if [[ -f ${HOME}/.zplug/init.zsh ]]; then
    export ZPLUG_LOADFILE=${HOME}/.zplug.zsh
    source ~/.zplug/init.zsh

    # Auto installer
    if ! zplug check --verbose; then
        printf "Install? [y/N]: "
        if read -q; then
            echo; zplug install
        fi
    fi

    # コマンドをリンクして、PATH に追加し、プラグインを読み込む
    zplug load

# 初回はzplugをcurlでインストール
else; printf "Install zplug? [y/N]: "
    if read -q; then
        curl -sL --proto-redir -all, https\
            https://raw.githubusercontent.com/zplug/installer/master/installer.zsh |
    zsh && source $0  # .zshrc再リロード
    fi
fi

tmux

  • .tmux.conf
  • .tmux/plugins/tpm

この辺で管理しています。
submoduleも入れています。

.gitmodules
[submodule ".tmux/plugins/tpm"]
    path = .tmux/plugins/tpm
    url = https://github.com/tmux-plugins/tpm

主な使い方

  • セッション切り替え
    • C-Space J
    • C-Space K
  • セッションデタッチ
    • C-Space d
  • ウィンドウの新規作成
    • C-Space c
  • ウィンドウの削除
    • C-Space x
  • ウィンドウ切り替え
    • C-Space L
    • C-Space H
  • ペイン切り替え
    • C-Space j
    • C-Space k
    • C-Space l
    • C-Space h
  • ペインの新規作成(横分割)
    • C-Space "
  • ペインの新規作成(縦分割)
    • C-Space %
  • コピー(ヤンク)
    • C-Space SpaceでコピーモードにはいってからSpaceと移動キーで選択してy
  • ペースト
    • C-Space p

nvim

エディタはneovimです。
dotfilesでは以下のファイルで管理しています。
プラグインマネージャはShougo/dein.vim

  • .config/nvim/init.vim: neovimが呼び出されるときにsourceする。これが親
  • .config/nvim/keymap.rc.vim :neovimのキーバインド
  • .config/nvim/options.rc.vim: neovimのオプションsetで設定するもの
  • .config/nvim/autocmd.rc.vim: autocmdで設定するもの
  • .config/nvim/ftplugin/arduino.vim: arduino使うときに入れてたけど今使ってない
  • .config/nvim/ftplugin/html.vim: htmlファイル開いたときのタブ設定など
  • .config/nvim/ftplugin/javascript.vim: jsファイル開いたときのタブ設定など
  • .config/dein/plugins.toml: 常に呼び出されるdein管理のプラグイン
  • .config/dein/lazy.toml: 特定の条件下で呼び出されるdein管理のプラグイン
  • .config/dein/lightline.toml: UIかっこよくする
  • .config/dein/vim-airline.toml: UIかっこよくする。今は使っていない
  • .config/dein/python.toml: python3+としてコンパイルされているneovimでないと使えないプラグイン

python環境

python環境を使い分ける〜pyenvとcondaのメモ〜

pyenv+condaを使ってpython環境の構築をする記事です。

2018年 vagrant + Virtual Box

Archlinux on Vagrant 日本語/GUI/docker セットアップスクリプト

この辺からドットファイルとパッケージだけではなくinfrastructure as code, 環境周りを一括して管理するようになりました。
これまでWindows機でCmder上でやっていたところ、watchコマンドが使えなかったりで本格的なLinuxを使いたいと思い、Windows10上にvagrant+VirtualBoxを使ってオレオレArchlinuxイメージを作って色々いじっていました。
Linuxマシンになってなるべく困難は調べものして解決するつもりでしたが、どうしてもうまく行かなくなったときは仮想マシンのスナップショット機能で過去に戻ればいいし、最悪新しい環境をもう一つ作ればいいやという軽い気持ちで使い始めました。

2019年 docker

この辺からWindows10を捨ててUbuntu18.04に移行しました。
VirtualBoxではパフォーマンス面でリソースを100%活かしきれていないことが気になったので、CPUやメモリをホストと共有できるコンテナ仮想環境dockerに開発環境を移行しました。ポータブルなので、一度イメージ構築してしまえば新しいマシンに移ってもイメージをpullするだけなので、引っ越しに抵抗がありませんね。
オレオレdocker開発環境を作ってみたにも自分用docker環境構築を書きました。
この記事ではさらにその続きで、docker-composeを使って各dockerコンテナをワンコマンドで立ち上げようという試みです。

docker-compose.yml
# maintainer: u1and0 <e01.ando60@gmail.com>
# version 3.7 for docker version 18.06+
# see https://docs.docker.com/compose/compose-file/
# What for:
#    docker in docker dev.env
#    master: zsh & zplug env. Pairents for all docker image.
#    python: python env. It can use ipython & jupyter
#
# Usage:
# $ cd /path/to/composeset
# $ docker-compose start master
# Starting master ... done
# Starting jupyter ... done
# Starting vimgo ... done
# $ docker exec -it composeset_master_1 zsh -l
#
version: '3'
services:

    master:
        image: "u1and0/myenv:latest"
        tty: "true"
        volumes:
            - "${HOME}:/home/vagrant"
            - "${HOME}/.zsh_history:/root/.zsh_history"
            - "${HOME}/yankring_history_v2.txt:/root/yankring_history_v2.txt"
            - "/mnt/e/Users/U1and0:/mnt"
            - "/var/run/docker.sock:/var/run/docker.sock"
        working_dir: "/home/vagrant"
        environment:
            - "HOST=master"
        command: "zsh"

    jupyter:
        # jupyter tagはpasswordを設定したイメージ
        # [Jupyter on Dockerでパスワードの設定方法が分からないあなたへ](https://qiita.com/Yusuke-Shimizu/items/731ca71b3c4c3649ec7f)
        image: "u1and0/pyenv:jupyter"
        volumes:
            - "${HOME}:/home/vagrant"
            - "/mnt/e/Users/U1and0:/mnt"
        working_dir: "/home/vagrant/Dropbox/Program/python"
        environment:
            - "PYTHONPATH=/home/vagrant/Dropbox/Program/python"
        command: "bash -c 'source /root/.pyenvrc && source activate && jupyter notebook --allow-root'"
        ports:
            - "8888:8888"

    vimgo:
        image: "u1and0/vim-go:zplug1.2.0-r1"
        tty: "true"
        working_dir: "/root/go/src"
        volumes:
            - "${HOME}:/home/vagrant"
            - "${HOME}/.zsh_history:/root/.zsh_history"
            - "${HOME}/yankring_history_v2.txt:/root/yankring_history_v2.txt"
            - "/mnt/e/Users/U1and0:/mnt"
        environment:
            - "GO111MODULE=on"
            - "HOST=go"
        ports:
            - "8080:8080"
        cap_add:
            - "SYS_PTRACE" # for golang debugger

dotfilesでは .docker/config.json でC-/ C-PからC-\ C-/にデタッチキーを変更しています。

docker/config.json
{
    "detachKeys": "ctrl-\\,/"
}
イメージ名 使用用途
image: "u1and0/myenv:latest" DinD
image: "u1and0/pyenv:jupyter" Python環境
image: "u1and0/vim-go:zplug1.2.0-r1" Go環境

使い方

$ cd /path/to/composeset                            # 1
$ docker-compose start master                       # 2
Starting master ... done
Starting jupyter ... done
Starting vimgo ... done
$ docker exec -it composeset_master_1 zsh -l        # 3
  1. docker-compose.ymlがある場所で
  2. docker-compose startするだけで、docker-compose.ymlに記載している全環境をワンコマンドで立ち上げます。 ハマりどころとしては、DinDをするためにホストマシンの/var/run/docker.sockをボリュームにマウントすること。そして、tty:trueとしておかないとcommandがzshなどのshellを指定しているimageが一瞬で落ちてしまうことです。
  3. docker exec ...で各環境にattachします。 とりあえずDinD(Docker in Docker)用のimage: masterにいつもログインして、tmuxを起動し、作業に入ります。

Goで開発するなら

$ docker exec -it composeset_vimgo_1 zsh -l

Pythonで開発するなら

$ docker exec -it composeset_jupyter_1 bash -c "source ~/.pyenvrc && source activate && ipython" 
$ docker exec -it composeset_jupyter_1 bash -c "source ~/.pyenvrc && source activate && nvim +PyenvActivate" 

commandにjupyter notebookを指定しているのでlocalhost:8888をブラウザのURL欄に打てばJupyter Notebookに接続できます。
Pythonパッケージマネジャにpyenv+condaを使っているのでログイン時必ずsource activateしないといけません。
source activateするためのパスが通っていないためにsource ~/.pyenvrcしないといけません。
.pyenvrcは自分で書いたものです。

.pyenvrc
#!/bin/sh
# pyenv config file

# pyenvのあるpath
export PYENV_ROOT="$HOME/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
# pyenv実行
eval "$(pyenv init -)"
# bin/activate bin/deactivateを使うため

PATH="$PYENV_ROOT/versions/miniconda3-latest/bin:$PATH"
PATH="/usr/bin:$PATH"
export PATH

PYTHONPATH="${HOME}/Dropbox/Program/python/:$PYTHONPATH"
PYTHONPATH="${HOME}/home/python/:$PYTHONPATH"
export PYTHONPATH

このへんまだまだベストな状態ではありません。Dockerでパッケージ管理するなら、condaなんて使わずにpipだけでいいのでわー?condaが依存パッケージ集めてくれるから楽なのか?今は「今までcondaで管理してきたから」という理由だけでcondaを使い続けています。

その他の開発するなら

masterイメージでshell scriptとかやったりします。qiita記事もこのイメージ内のneovimで作成しています。

2020年以降は

またVT-x系仮想環境に戻ろうと思います。
その上でDocker立ち上げようかと。
なぜなら、ホストマシンの電源を切ると開発の流れが途切れるからです。
常用ではホストマシンは就寝前にスリープモードにするのですが、ホストマシンのアップデートとかはいったりなんか調子おかしくなったときはリブートせざるを得ないときが必ずあります。

上で説明したdotfilesにはターミナルの復元ツールが多数含まれています。

  • すべてのファイルはDropboxに保存して、万一ロールバックしたいときでも簡単に復元できる。
  • そもそもgitが使えばコードの差分を見ながら過去に戻れたり別バージョンを残しておいたりできる。
  • tmux-plugins/tmux-resurrectでtmuxのセッションを復元できる。Space C-rに割り当てています。
  • tpope/vim-obsessionでneovim/vimのセッションを復元できる。立ち上げるときにnvim -SというSオプションをつける。
  • docker-composeもdocker-compose.ymlのディレクトリに飛んでdocker-compose startするだけでコンテナ郡は一発復元する。

このように復元方法には困らないのですが、しかしながら一回途切れるものは途切れる。そのへんやっぱり仮想マシン用意してセッションを保存しておけばホストの電源落としてもかならずセッション復元するし、スナップショットも取れる。そしてDockerに移住したもののそんなにリソースを食う処理って滅多にやらない。

そんなわけでまたVirtualBoxかなんかに戻ろうと思います。

今注目しているのがkvmmultipass
multipassはこの記事「仮想環境multipassを試す」で少しいじってみた。VirtualBoxでやったところで、結局CUI環境はDockerで構築するわけなので、間に挟むイメージはなんでもいい、ということで安直にUbuntu一択になってしまうわけです。multipassは選べるイメージがUbuntuのみでシンプルに作られているので気に入りました。
しかしながら、ポートフォワーディングが今のところできないみたいなので、サーバー・ブラウザ・web使う系の開発に使えないのでまだ様子見です。
kvmはインストールがうまくいかないので、試行錯誤中です。
時間かかるようだったらvagrant+VirtualBoxで良いかなと思います。慣れているし。

まとめ

開発環境構築は永遠に終わらない開発の一つです。僕のdotfilesは一人でコミット数が719件になりました。
今後も快適な開発のための開発環境作りに勤しんで参りたいと思います。
今年もアドベントカレンダーに多くのCUIツールが紹介されていたので、試したくてウズウズしているところです。

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