20190523のdockerに関する記事は11件です。

Let's Encrypt のワイルドカード証明書を取得して、rancherに設定する

最初に

この記事は、備忘録です。

Let’s Encryptがワルドカード証明に対応するのを待ちわびていたことをふと思い出したので、開発環境のRancher環境ように取得して、設定してみたというものです。

環境

サクッと試したかったので、Ubuntu16.04コンテナで取得しました。

  • DNS:AuzreのDNS
  • コンテナ:手元のMacの上

手順

参考にした記事によると、公式では「dns-plugin」を使うと書いてありますが、面倒なので元記事と同じく手動で取得しました。

証明書の取得

CertBot環境を作る。と言っても、使い捨てのコンテナだけど。

❯ docker run -it --rm ubuntu:16.04  bash                                                                                  
root@ad7f41c5773c:/# apt update
・・・
Building dependency tree       
Reading state information... Done
2 packages can be upgraded. Run 'apt list --upgradable' to see them.
root@ad7f41c5773c:/# apt upgrade < 念のため。。。
Reading package lists... Done
・・・
root@ad7f41c5773c:/# apt install certbot <ここからが本番
・・・
Get:49 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 certbot all 0.23.0-1~ubuntu16.04.1 [17.4 kB]              
Fetched 16.0 MB in 17s (932 kB/s)                                                                                               
E: Failed to fetch http://61.26.74.210:80/pdata/07e41ece3becb885/archive.ubuntu.com/ubuntu/pool/main/p/python-pbr/python-pbr_1.8.0-4ubuntu1_all.deb  Writing more data than expected (15617 > 12552)

E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?
root@ad7f41c5773c:/# apt install --fix-missing certbot <再度
・・・
148 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done. 
root@ad7f41c5773c:/# certbot --version
certbot 0.23.0 < 0.22.0以上なので大丈夫

証明書の取得

root@ad7f41c5773c:/# certbot certonly --manual --preferred-challenges dns -d *.[自分のドメイン] --server https://acme-v02.api.letsencrypt.org/directory
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): [自分のメールアドレス。期限切れ通知が届くので良く見るやつにしないと痛い目見ます]
Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org

-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A

-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: Y
Starting new HTTPS connection (1): supporters.eff.org
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for [自分のドメインが表示される]

-------------------------------------------------------------------------------
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
-------------------------------------------------------------------------------
(Y)es/(N)o: 
(Y)es/(N)o: Y

-------------------------------------------------------------------------------
Please deploy a DNS TXT record under the name
_acme-challenge.[自分のドメインが表示される] with the following value:

xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxx-x

Before continuing, verify the record is deployed.
-------------------------------------------------------------------------------
Press Enter to Continue  < 自分のDNSに上記のテキストレコード追加するまでEnterは触らない。
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/[自分のドメイン]/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/[自分のドメイン]/privkey.pem
   Your cert will expire on 2019-08-21. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

root@ad7f41c5773c:/# cd /etc/letsencrypt/live/
root@ad7f41c5773c:/etc/letsencrypt/live# ll
total 12
drwx------ 3 root root 4096 May 23 13:14 ./
drwxr-xr-x 9 root root 4096 May 23 13:14 ../
drwxr-xr-x 2 root root 4096 May 23 13:14 [自分のドメイン]/
root@ad7f41c5773c:/etc/letsencrypt/live# cd [自分のドメイン]/
root@ad7f41c5773c:/etc/letsencrypt/live/[自分のドメイン]# ll
total 12
drwxr-xr-x 2 root root 4096 May 23 13:14 ./
drwx------ 3 root root 4096 May 23 13:14 ../
-rw-r--r-- 1 root root  543 May 23 13:14 README
lrwxrwxrwx 1 root root   39 May 23 13:14 cert.pem -> ../../archive/[自分のドメイン]/cert1.pem
lrwxrwxrwx 1 root root   40 May 23 13:14 chain.pem -> ../../archive/[自分のドメイン]/chain1.pem
lrwxrwxrwx 1 root root   44 May 23 13:14 fullchain.pem -> ../../archive/[自分のドメイン]/fullchain1.pem
lrwxrwxrwx 1 root root   42 May 23 13:14 privkey.pem -> ../../archive/[自分のドメイン]/privkey1.pem

いつもの通り証明書が取得できている。

Azure DNSへのテキストレコード登録

・・・特に書くこともないです・・・登録後に「Press Enter to Continue」を。

スクリーンショット 2019-05-23 22.49.20.png

Rancherへの登録

インフラ->証明書から、証明書追加を選択。

スクリーンショット 2019-05-23 22.55.04.png

で保存する。正しければ、アクティブになります。
スクリーンショット 2019-05-23 22.58.13.png

ロードバランサーへの登録

ロードバランサーの編集画面の一番下のプルダウンに、登録した証明書が表示されているはず。この時、個別に取得してた証明書の設定を外しました。

スクリーンショット 2019-05-23 22.59.30.png

確認

素晴らしい。。

スクリーンショット 2019-05-23 23.01.43.png

雑感

今まではNginx経由で取得していたが、DNS操作でサクッと作れるのはとても助かります。今回設定したrancherは開発環境のため、サブドメインがたくさんありSSL証明書の設定が面倒でしたが、ワイルドカード証明書は素敵です。

これから

  • 自動取得(現rancher環境)
  • Kube環境への適用
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一足遅れて Kubernetes を学び始める - 10. config&storage その1 -

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -
  9. 一足遅れて Kubernetes を学び始める - 09. discovery&LB その2 -
  10. 一足遅れて Kubernetes を学び始める - 10. config&storage その1 -

前回

一足遅れて Kubernetes を学び始める - 08. discovery&LB その2 -では、様々なserviceを学習しました。
今回は、config&storageのconfigを学びます。

config&storage

Kubernetesには、下記のようにリソースの種類が存在します。

リソースの分類 内容
Workloadsリソース コンテナの実行に関するリソース
Discovery&LBリソース コンテナを外部公開するようなエンドポイントを提供するリソース
Config&Storageリソース 設定・機密情報・永続化ボリュームなどに関するリソース
Clusterリソース セキュリティやクォータなどに関するリソース
Metadataリソース リソースを操作する系統のリソース

KubernetesのWorkloadsリソース(その1)

環境変数

静的設定や、Podやコンテナの情報を設定、シークレットでの設定があるみたいです。

sample-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-env
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.12
      env:
        - name: MAX_CONNECTION
          value: "100"
        - name: POD_IP
          valueFrom:
           fieldRef:
            fieldPath: status.podIP
        - name: LIMITS_CPU
          valueFrom:
            resourceFieldRef:
             containerName: nginx-container
             resource: limits.cpu
pi@raspi001:~/tmp $ k apply -f sample-env.yaml
pi@raspi001:~/tmp $ k exec -it sample-env env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
...
MAX_CONNECTION=100
POD_IP=10.244.1.97
LIMITS_CPU=4
...

MAX_CONNECTIONは、静的に設定できています。
Podやコンテナの設定は、POD_IP,KIMITS_CPUで設定できています。
Podやコンテナの情報は、k get pods sample-env -o yamlで得ることができます。ふむふむ。

Secret

パスワードなどの機密情報をSecretで暗号化してくれます。
手段の種類が下記のようにいくつかあります。

  • Generic
  • TLS
  • Docker Repository
  • Service Account

Genericの場合は、スキーマレスなため、汎用性の高い指定が可能になります。それを使ってみようと思います。(TLSの場合は、tls.crt,tls.keyが必要)

使い方して、ファイル参照、envfile参照、直接指定、マニュフェスト指定の4パターンです。それぞれ試してみます。

ファイル参照

pi@raspi001:~/tmp $ echo -n "root" > ./username
pi@raspi001:~/tmp $ echo -n "rootpassword" > ./password
pi@raspi001:~/tmp $ k create secret generic --save-config sample-db-auth --from-file=./username --from-file=./password
pi@raspi001:~/tmp $ sudo apt-get install jq
pi@raspi001:~/tmp $ k get secrets sample-db-auth -o json | jq .data
{
  "password": "cm9vdHBhc3N3b3Jk",
  "username": "cm9vdA=="
}

envfile参照

env-secret.txt
username=root
password=rootpassword
pi@raspi001:~/tmp $ k create secret generic --save-config sample-db-auth2 --from-env-file ./env-secret.txt
pi@raspi001:~/tmp $ k get secrets sample-db-auth2 -o json | jq .data
{
  "password": "cm9vdHBhc3N3b3Jk",
  "username": "cm9vdA=="
}

直接指定

pi@raspi001:~/tmp $ k create secret generic --save-config sample-db-auth3 --from-literal=username=root --from-literal=password=rootpassword
pi@raspi001:~/tmp $ k get secrets sample-db-auth3 -o json | jq .data
{
  "password": "cm9vdHBhc3N3b3Jk",
  "username": "cm9vdA=="
}

マニュフェスト指定

sample-db-auth.yaml
apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth4
type: Opaque
data:
  username: cm9vdA== # root
  password: cm9vdHBhc3N3b3Jk # rootpassword
pi@raspi001:~/tmp $ k apply -f sample-db-auth.yaml
pi@raspi001:~/tmp $ k get secrets sample-db-auth4 -o json | jq .data
{
  "password": "cm9vdHBhc3N3b3Jk",
  "username": "cm9vdA=="
}

どれも、正しく動きましたね。プロダクトとしては使わないと思いますが、お試しで確認するには
Genericは扱いやすくて良いですね。

では、設定した値を使ってみましょう。

Secretの利用

手段として、環境変数かVolumeかの2つです。

環境変数からSecretを使う

sample-secret-single-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-single-env
spec:
  containers:
    - name: secret-container
      image: nginx:1.12
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: sample-db-auth
              key: username
pi@raspi001:~/tmp $ k apply -f sample-secret-single-env.yaml
pi@raspi001:~/tmp $ k exec -it sample-secret-single-env env | grep DB_USERNAME
DB_USERNAME=root

環境変数から使う場合、値が固定されてしまいます。(静的)

VolumeからSecretを使う

sample-secret-single-volume.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-secret-single-volume
spec:
  containers:
    - name: secret-container
      image: nginx:1.12
      volumeMounts:
      - name: config-volume
        mountPath: /config
  volumes:
    - name: config-volume
      secret:
        secretName: sample-db-auth
        items:
        - key: username
          path: username.txt
pi@raspi001:~/tmp $ k apply -f sample-secret-single-volume.yaml
pi@raspi001:~/tmp $ k exec -it sample-secret-single-volume cat /config/username.txt
root

こちらは、動的に書き換えることができるそうです。逐次Volumeを見ているんでしょうね。(環境変数の場合、コンテン起動した時点で固定される)

pi@raspi001:~/tmp $ cat << EOF | k apply -f -
> apiVersion: v1
> kind: Secret
> metadata:
>   name: sample-db-auth
> type: Opaque
> data:
>  username: YMRtaW4=
>  # root > admin
> EOF
pi@raspi001:~/tmp $ k exec -it sample-secret-single-volume cat /config/username.txt
amin

動的に書き換わっていますね。OK!
※ adminのaが文字化けしていた...

ConfigMap

設定情報をKey-Value形式で登録することができます。
こちらも手段としては、ファイル参照、直接参照、マニフェスト参照があります。
さっきと同じなので、ファイル参照のみ試してみます。

sample.txt
hogehoge
fugafuga
pi@raspi001:~/tmp $ k create configmap --save-config sample-configmap --from-file=./sample.txt
pi@raspi001:~/tmp $ k get configmaps sample-configmap -o json | jq .data
{
  "sample.txt": "hogehoge\nfugafuga\n"
}

secretと同じ感じですね。これって、どんなファイルでも(1MBまで)保存できちゃうそうです。
secretと同様で、設定したデータは環境変数、Volumeの2つから参照可能です。

お片付け

pi@raspi001:~/tmp $ k delete -f sample-env.yaml -f sample-db-auth.yaml -f sample-secret-single-env.yaml -f sample-secret-single-volume.yaml
pi@raspi001:~/tmp $ k delete secret sample-db-auth sample-db-auth2 sample-db-auth3 
pi@raspi001:~/tmp $ k delete configmap sample-configmap

最後に

環境変数の設定方法について、学びました。
個人開発で、外部サービスをアプリケーションに組み込む際、
API_KEYを環境変数として登録して開発しています。
今回は、GenericでSecretを保存しましたが、プロダクトでは、
service_accountを使うのが一般的なのでしょうか?

次回は、Storageについて学習します。

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

docker redmineへpluginをインストール 備忘録

docker redmineへPlug-inをインストールする

Redmineのメニューなどの文字を置き換えるプラグイン。

Redmine.tokyoで紹介された
redmine_message_customize
https://github.com/ishikawa999/redmine_message_customize

前回、dockerでredmineをデプロイした環境の続きから
作業してます。

Gitからインストールします。
dockerの状態確認をする。
ps -a この状態から開始します。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS                    NAMES
517586f4b9b5        redmine             "/docker-entrypoint.…"   2 days ago          Exited (255) About a minute ago   0.0.0.0:3000->3000/tcp   some-redmine
8695e2683fc5        postgres            "docker-entrypoint.s…"   2 days ago          Exited (255) About a minute ago   5432/tcp                 some-postgres
$docker exec -it some-redmine bash
root@517586f4b9b5:/usr/src/redmine# 
bundle install --without development test
cd plugins
git clone https://github.com/ishikawa999/redmine_message_customize

Cloningされます。

再起動する。

docker stop redmine
docker start redmine

RunTestする

$ docker exec -it some-redmine bash
# bundle exec rake test TEST=plugins/redmine_message_customize/test RAILS_ENV=tes

スクリーンショット 2019-05-23 21.46.56.png

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

Dockerのコンテナのロケール設定(ubuntu16.4ではうまくいかないという話)

はじめに

Dockerのコンテナのローケール設定がUTCから変更できない。
解決先は試していないが、状況を記載する。

状況

事実

  • RUN ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtimeをしたが、起動したコンテナはUTCのまま。
  • 起動したコンテナ上でls -l /etc/localtimeとすると、リンクは貼られている
  • そもそもzoneinfo配下がなかった
  • ubuntu14.4ではRUN ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtimeでJSTになる

未確認情報

  • ubuntu16.4ではzoneinfoが省かれているので追加パッケージが必要
  • apt-getで入れる方法があるらしい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails hands on

rails で Web アプリを作ってみよう!

Web アプリを作って、動かして、触って、デプロイしてみよう!

事前準備

docker , bundler はインストールしておいてね

docker

bundler

docker-compose.yml
Dockerfile
docker

をローカルにコピー
TODO: curl でダウンロードできるようにする

やってみよう!

$ mkdir rails_sample && cd rails_sample
$ cp -a ~/Download/Dockerfile ~/Download/docker-compose.yml ~/Download/docker .
remove `Gemfile.lock` in Dockerfile
$ bundle init
$ docker-compose run --rm web bundle exec rails new . -d mysql --skip-test

ここで今日のゴールを
https://rails-sample-fot-lt.herokuapp.com/users/sign_in
(今日はログインやらんけど)

ログインあり&デプロイありの、ハンズオンやりたいと思う人!

希望あれば日程調整しましょう
3時間ぐらい
うまくいけば30分

edit database.yml

  pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
  username: <%= ENV.fetch('MYSQL_USERNAME') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: <%= ENV.fetch('MYSQL_HOST') { '127.0.0.1' } %>
  port: <%= ENV.fetch('MYSQL_PORT') { 3306 } %>

add Gemfile.lock in Dockerfile

$ docker-compose up
$ docker-compose exec web bin/rails g scaffold post content
$ docker-compose exec web bin/rails db:migrate

hands on メモ

$ mkcd rails_sample
$ cp -a ~/Download/Dockerfile ~/Download/docker-compose.yml ~/Download/docker .

remove Gemfile.lock in Dockerfile

$ bundle init
$ docker-compose run --rm web bundle exec rails new . -d mysql --skip-test
create remote repository
$ cp -a ~/Download/Gemfile  .

edit database.yml

database.yml
  pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
  username: <%= ENV.fetch('MYSQL_USERNAME') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: <%= ENV.fetch('MYSQL_HOST') { '127.0.0.1' } %>
  port: <%= ENV.fetch('MYSQL_PORT') { 3306 } %>

add Gemfile.lock in Dockerfile

$ docker-compose up
$ docker-compose exec web bundle install
$ docker-compose exec web rails generate simple_form:install
$ docker-compose exec web rails haml:erb2haml
$ docker-compose exec web rails g annotate:install
$ docker-compose exec web bin/rails g scaffold post content
$ docker-compose exec web bin/rails g scaffold user name
$ docker-compose exec web bin/rails db:migrate
$ docker-compose exec web bundle exec rails generate devise:install

add

    %p.notice= notice
    %p.alert= alert

in app/views/layouts/application.html.haml

$ docker-compose exec web bundle exec rails g devise:views
$ docker-compose exec web rails generate devise user
$ docker-compose exec web bin/rails db:migrate

add before_action :authenticate_user! in app/controllers/posts_controller.rb and app/controllers/users_controller.rb

add root to: 'post#show' route.rb

access http://localhost:3000/users

heroku deploy

create app in gui

$ heroku login
$ heroku git:remote -a rails-sample-fot-lt
$ git push heroku master
$ heroku addons:create cleardb:ignite
$ heroku config | grep CLEARDB_DATABASE_URL

mysql://[username]:[password]@[hostname]/[db_name]?reconnect=true

$ heroku config:add DB_NAME="[db_name]"
$ heroku config:add DB_USERNAME="[username]"
$ heroku config:add DB_PASSWORD="[password]"
$ heroku config:add DB_HOSTNAME="[hostname]"
$ heroku config:add DB_PORT="3306"
$ heroku run rake db:migrate

コピー用

Dockerfile
FROM ruby:2.6.2

RUN apt-get update --fix-missing \
  && apt-get -y upgrade \
  && apt-get install -qq -y \
    nodejs \
    mysql-client \
  && apt-get autoremove -y \
  && apt-get clean all \
  && fc-cache -f -v

# Add dumb init to process system events
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 -O /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init

RUN gem update --system
RUN gem install bundler

WORKDIR /rails_sample
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs=4 --retry=3

COPY . .

EXPOSE 3000

# Start the main process.
CMD bin/rails server -b 0.0.0.0
docker-compose.yml
version: '3.7'

services:
  db:
    restart: always
    image: mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - 3306:3306
    volumes:
      - ./docker/mysql/conf:/etc/mysql/conf.d:rw
      - dbdata:/var/lib/mysql

  web:
    build: .
    depends_on:
      - db
    volumes:
      - .:/rails_sample
      - "bundle:/usr/local/bundle"
    environment:
      - MYSQL_HOST=db
    command: >
      bash -c "
        bundle install --quiet &&
        docker/bin/start rails"
    ports:
      - 3000:3000

volumes:
  bundle:
  dbdata:
docker/mysql/conf/default_authentication.cnf
[mysqld]
default_authentication_plugin=mysql_native_password
docker/bin/rails
#!/bin/sh

docker-compose run --rm backend bin/rails "$@"
docker/bin/start
#!/bin/sh

if [ $# = 0 ]; then
  echo "usage:"
  echo "  start rails:              Start rails server"
  echo "  start rails recreate db:  Start rails server recreate db"
  echo "  start sidekiq:            Start sidekiq process"
  echo "  start sidekiq-web:        Start sidekiq web server"
  exit 1
fi

case "$1" in
  "rails")
    bundle exec rails tmp:pids:clear
    bundle exec rails log:clear tmp:clear
    bundle exec rails server --port 3000 --binding "0.0.0.0"
    ;;
  "rails recreate db")
    bundle exec rails tmp:pids:clear
    bundle exec rails db:reset
    bundle exec rails db:migrate
    bundle exec rails log:clear tmp:clear
    bundle exec rails server --port 3000 --binding "0.0.0.0"
    ;;
  "rails migrate db")
    bundle exec rails db:migrate
    ;;
  "sidekiq")
    bundle exec sidekiq
    ;;
  "sidekiq-web")
    bundle exec rackup --host "0.0.0.0" --port 9292 lib/sidekiq_web/config.ru
    ;;
esac
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Piで学ぶdocker入門

はじめに

本文書を参照頂きありがとうございます。
この文書は社内勉強会に向けた文書である為、一部期待値と異なる表現、記載がある場合がありますので、ご利用になられる際はその辺りも加味して頂いてご利用下さい。

勉強会お題

  • 第01回(2019.05.22):Raspberry Piのセットアップ、Docker環境のセットアップ
  • 第02回
  • 第03回
  • 第04回
  • 第05回
  • 第06回
  • 第07回
  • 第08回
  • 第09回
  • 第10回
  • 第11回

Special Thanks

※貴重な技術文書の参照先の皆様、リンク先等のご紹介

@kotaro-drさん>
【図解】Dockerの全体像を理解する -前編-
【図解】Dockerの全体像を理解する -中編-
【図解】Dockerの全体像を理解する -後編-

@MahoTakaraさん>
今さら人に聞けない Kubernetes とは?

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

Serverless Framework を Docker で動かす Dockerfile テンプレート

概要

Serverless Framework のコマンドを Mac で叩きたかったがローカル環境を汚したくなかったので
Docker コンテナ上で叩けるような Dockerfile を作成しました。

環境

Mac
Docker version 18.09.2, build 6247962
docker-compose version 1.23.2, build 1110ad01

作成した Dockerfile

FROM python:3.7-alpine
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY

ENV NODE_PATH /usr/lib/node_modules/
# install nodejs
RUN apk update \
  && apk add --no-cache nodejs npm

# install aws-cli
RUN pip install awscli

# install serverless framework
RUN npm install -g serverless serverless-plugin-existing-s3

# set aws key 
RUN sls config credentials --provider aws --key $AWS_ACCESS_KEY_ID --secret $AWS_SECRET_ACCESS_KEY

# change work directory
RUN mkdir -p /app
WORKDIR /app

作成した docker-compose ファイル

version: '3.5'
services:
  serverless:
    build: 
      context: .
      dockerfile: ./dockerfiles/serverless/Dockerfile
      args: 
        - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
        - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
    tty: true
    stdin_open: true
    image: serverless
    working_dir: /app
    volumes:
      - .:/app
    container_name: serverless

解説

Docerfile の設定

まず Dockerfile について解説します

コンテナの設定

FROM python:3.7-alpine
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY

ENV NODE_PATH /usr/lib/node_modules/

aws cli をインストールするために alpine linux の python をインストールします。
OS は何でもいいのですが alpine linux は容量が少ないのでよく採用させてもらってます。
ちなみに python:3.7-alpine は 87MB でした。

次に docker-compose から渡された AWS のアクセスキーとシークレットキーを読み込ませます。
こちらはサーバレスフレームワークを利用する上で必要になります。
最後に NODE_PATH の環境変数を設定します。 NODE_PATH は npm のグローバルインストールを行うための
node_module フォルダパスを指定します。

パッケージのインストール

# install nodejs
RUN apk update \
  && apk add --no-cache nodejs npm

# install aws-cli
RUN pip install awscli

# install serverless framework
RUN npm install -g serverless serverless-plugin-existing-s3

次にパッケージのインストールです。
RUN コマンドを利用して alpine-linux の apk コマンドを叩きます。
サーバレスフレームワークは npm で管理されているので npmnodejs をインストールします。
また、裏で AWS CLI コマンドが動くので awscli を pip コマンドでインストールしています。
npm install コマンドでグローバルに serverless フレームワークと関連するプラグインをインストールしています。
作成済みの S3 バケットに対して後から lambda イベントを付与したかったので serveless-plugin-existing-s3
プラグインも入れてます。

https://github.com/matt-filion/serverless-external-s3-event

sls コマンドの実行

# set aws key 
RUN sls config credentials --provider aws --key $AWS_ACCESS_KEY_ID --secret $AWS_SECRET_ACCESS_KEY

# change work directory
RUN mkdir -p /app
WORKDIR /app

最後に sls コマンドを実行して アクセスキーとシークレットキーの設定を実行しています。

docker-compose の設定

version: '3.5'
services:
  serverless:
    build: 
      context: .
      dockerfile: ./dockerfiles/serverless/Dockerfile
      args: 
        - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
        - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
    tty: true
    stdin_open: true
    image: serverless
    working_dir: /app
    volumes:
      - .:/app
    container_name: serverless

docker-compose は特に複雑なことはせず Mac のローカルに設定されている環境変数を args で Dockerfile に渡してあげています。
dockerfile は先程作成した dockerfile のパスを指定します。
作成した serverless.yml ファイルをコンテナ側でも参照できないと行けないので volume をマウントしてあげましょう。

解説した dockerfile と docker-compose のファイルは github においています。
ご自由にお使いください。

https://github.com/syoimin/serverless-cfn

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

Dockerでlaravelの環境構築

Dockerを使用したlaravel環境構築

laradockを使用することで簡単にlaravelの環境構築をすることができるので、以下に手順をまとめる。

開発環境

MacOS Mojave
既にDocker GitはMacに導入済として下記記述します。

laradockのダウンロード

Gitを使用して、任意のフォルダにlaradockをダウンロードします。

git clone https://github.com/Laradock/laradock.git
// laradockへ移動
cd laradock
// 公式ページの記載通り、下記コマンドを入力
cp env-example .env

Laradock公式サイト

Mysqlのバージョンを指定(現行最新版の8だと、通常とは違う認証になり、詰まる可能性大。指定がなければ、5.7を使用したほうが無難)

// .envを開き、"MYSQL_VERSION=latest" を以下のように変更
MYSQL_VERSION=5.7

コンテナの作成

開発に必要なプログラムを指定し、コンテナを作成。なお、プログラムを指定しないとすべてのコンテナを作成しようとするため、時間がかかります。
通常のlaravel開発であれば、下記コマンドの指定で問題ないかと思います。

docker-compose up -d nginx mysql workspace phpmyadmin

終了後、http://localhostにアクセスすると、404 Not Foundが表示されます。
これにより、Nginxに無事アクセスできたことが確認できます。

laravelアプリの作成

コンテナのworkspace内にて、laravelアプリを作成します。

// docker ps にて、workspaceのコンテナID確認後、下記コマンドでコンテナ内に入る
docker-compose exec -it workspaceのコンテナID bash
// コンテナに入り、"/var/www$"にて、下記コマンドを実行。バージョンは5.5以外でも変更できます。
composer create-project laravel/laravel sample --prefer-dist "5.5.*"

これによって、sampleというアプリファイルが作成されます。
次に、laradockディレクトリの.envに、このアプリを作成したことを伝えるため、下記のように記述します。

### Paths #################################################

# Point to the path of your applications code on your host
APP_CODE_PATH_HOST=../sample

設定を反映させるため、dockerを再起動させます。

// 停止
docker-compose stop
// 再起動。以後は、nginxとmysqlのみを指定すればよい
docker-compose up -d nginx mysql

再度ブラウザでhttp://localhostにアクセスするとlaravelの画面が表示されます。

MySQLとの接続

MySQLコンテナと、laravelのDB設定を合わせます。
sampleフォルダ内の.envファイルを開き、下記のように記述。
この内容はlaradockフォルダの.env内のMySQLの記述と合わせたものになります。

// laravelアプリ内の.envファイル
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret

参考:laradockフォルダ.envのMySQL記述内容

### MYSQL #################################################

MYSQL_VERSION=5.7
MYSQL_DATABASE=default
MYSQL_USER=default
MYSQL_PASSWORD=secret
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d

再度、workspaceコンテナに入り、migrateを実行

php artisan migrate

MySQLコンテナに入り、テーブルが確認できれば完了です。

mysql -u default -p -h 127.0.0.1
show tables from default

参照

Laravel & Docker 環境構築 with Laradock
Laradockを使ったLaravel開発環境構築のやさしい解説
Laradock公式サイト

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

【Docker超入門】DockerでつくるLaTex環境

本稿に関してツッコミなどがあれば適宜コメントいただけるとありがたいです.

はじめに

大学の研究会で講義を1コマ持つことになったため,弊研究会では必須なLaTeX環境をDockerで作る方法を解説することにしました.

読者としてはDockerの知識が一切ない人を想定しています.

なお,本稿ではLaTex自体の使い方に関してはあまり触れません.

LaTexとは?

Latexとは,Donald E. Knuth氏が開発したレイアウトシステムのTexをLeslie Lamport氏が文書作成のために改良したものだそうです.主に学術論文を書く際に使用されており,数式を綺麗にレイアウトしてくれます.

例えば,世界一美しいとされるオイラーの等式でさえ,テキストで表現すると美しくないですが,

e^{iπ} + 1 = 0

TeXを用いてコンパイルすれば,以下のように美しくレイアウトされます.

$e^{iπ} + 1 = 0$

Dockerとは?

Dockerとはコンテナ仮想化技術のプラットフォームです.

image.png

従来の仮想化技術では,ホストOS上の仮想ハードウェアや,Hypervisorを利用してゲストOSを動かしていたのに対し,DockerではDocker EngineがホストOSのカーネルの利用を上手く管理することでプロセスとユーザを隔離しているため,VMに比べて軽量な仮想化環境を実現しています.

事前準備

本稿では,このDockerを用いてLatexのディストリビューションであるTexLiveの環境を行うため,まずはDockerと生成されたPDFの閲覧用にSkimをインストールします,

Dockerのインストール

下記ページからDockerHubのアカウントを作成しdmgをダウンロードしてインストールします.
MAC=> https://docs.docker.com/docker-for-mac/install/
Windows=> https://docs.docker.com/docker-for-mac/install/

Mac用Skim(軽量PDFビューワー)のインストール

下記ページよりダウンロードしてインストール
https://skim-app.sourceforge.io/

全体像を把握する

今回は下記のような構成でDockerを用いてLaTeX環境を構築します.

image.png

とりあえずDockerでLaTex環境を建ててみる

DockerとSkimのインストールが完了したら,筆者が事前に用意したLatexLiveのDockerイメージ(nontan18/latexmk)を用いて以下のコマンドでLaTex環境を実際に構築してみましょう.

// 作業用のworkディレクトリを作成し移動
$ mkdir work && cd work

workディレクトリに入ったら下記の内容のファイルを作成します.

sample.tex
\documentclass[twocolumn, a4j]{article}
\usepackage{multirow}
\usepackage{amsmath,amssymb}
\usepackage[T1]{fontenc}
% \usepackage[dvipdfmx]{graphicx}

\title{サンプル用のTexファイル}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\author{YOUR NAME\footnotemark[2] hoge@sfc.wide.ad.jp}
\renewcommand{\thefootnote}{\arabic{footnote}}
\date{\today}

\begin{document}

\twocolumn[
\begin{@twocolumnfalse}
  \maketitle
  \vspace{-6mm}
  \begin{abstract}
    ここに概要を書きましょう.
  \end{abstract}
  \vspace{2mm}
\end{@twocolumnfalse}
]

\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\footnotetext[2]{慶應義塾環境情報学部 村井研}
\renewcommand{\thefootnote}{\arabic{footnote}}

\section{はじめに}

   hoge hoge hoge hoge

\renewcommand{\refname}{参考文献}
\begin{thebibliography}{数字}
  \bibitem[Shigeo 2000]{Shigeo 2000} Test 
\end{thebibliography}

\end{document}

// 生成したdocument.texをdocument.pdfにコンパイル
$ docker run -v $(pwd):/root/work -it nontan18/texlive latexmk --pvc ./sample.tex

これでworkディレクトリ内にsample.pdfが出力されますので,SkimなどのPDFビューワーを使って表示してみましょう.

表示できたら,お好きなエディタを用いて,sample.texを編集してみます.

$ vim sample.tex

編集が完了すると自動でコンパルが始まり,document.pdfが更新されるかと思います.

Dockerの基本的な使い方

このセクションでは上記のDockerコマンドで一体何が起きているのかを説明します.

docker run

docker runはDockerイメージを元にDockerコンテナを起動するコマンドです.
runコマンドの第1引数で指定したコマンドが起動したコンテナ内で実行されます.

上記の

$ docker run -v $(pwd):/root/work -it nontan18/texlive latexmk --pvc ./sample.tex

においてはlatexmk --pvc ./sample.texコマンドが実行されています.
(latexmk --pvc ./sample.texコマンドは任意のTexファイルを監視して更新されるたびに自動コンパイルするコマンドです.)

-tオプションでDockerイメージを参照することができます.
上記の例ではDockerHubに登録されているnontan18/texliveというイメージを参照して,それを元にDockerコンテナを作成しています.
(ちなみに,このDockerHubのようなDockerImageが登録されている場所をDockerRegistryと呼びます.)

-vオプションはボリュームのマウントオプションで,DockerホストのボリュームをDokcerコンテナ内の任意の場所にマウントすることができます.上記の例では,texの作業用に作った
(-v オプションでは相対リンクでボリュームを指定できないので,pwdコマンドで現在のディレクトリを取得してそれを引数に取っています.)

docker ps

docker runコマンドで動いているDockerコンテナはdocker psコマンドで確認することができます.

$ docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                               NAMES
1349e83ba710        nontan18/texlive            "latexmk --pvc ./sam…"   5 seconds ago       Up 3 seconds                                            gallant_galileo

docker exec

また,docker exec -it [CONTAINER ID] [COMMAND]コマンドを使うと,起動中のコンテナ内でコマンドを実行することができます.

// $CONTAINER_IDはdocker psで取得したコンテナのIDに変更(example:1349e83ba710)
$ docker exec -it $CONTEAINER_ID bash
// ここからコンテナ内
root@1349e83ba710:~/work# ls
sample.aux  sample.dvi  sample.fdb_latexmk  sample.fls  sample.log  sample.pdf  sample.synctex.gz  sample.tex
// TeXLiveの絵文字パッケージが入っているか調べる
root@1349e83ba710:~/work# tlmgr search fontawesome
// コンテナ内にTeXLiveの絵文字パッケージをインストール
root@1349e83ba710:~/work# tlmgr install fontawesome
//...省略
tlmgr: package repository http://mirror.utexas.edu/ctan/systems/texlive/tlnet (verified)
[1/1, ??:??/??:??] install: fontawesome [498k]
running mktexlsr ...
done running mktexlsr.
running updmap-sys ...
done running updmap-sys.
tlmgr: package log updated: /usr/local/texlive/2019/texmf-var/web2c/tlmgr.log
// コンテナから出る
root@1349e83ba710:~/work# exit
exit
// ホストのシェルに戻った
$

上記の例では先程建てたコンテナ内でbashコマンドを実行して,ターミナルからコンテナ内に入り,TeXLiveの絵文字パッケージ(fontawsome)が入っていないことを確認して,TeXLiveパッケージマネージャー(tlmgr)を用いて絵文字パッケージをインストールしました.

docker commit(※あんま使わない)

ただ,これだけではDockerコンテナ内での変更でしか無いため,一度Dockerコンテナを削除し,docker runコマンドで再度コンテナを建てると,絵文字パッケージはインストールされていない状態に戻ってしまいます.

このDockerコンテナの変更をDockerイメージに反映させるためにはdocker commit [CONTAINER ID] [IMAGE NAME]コマンドを用います.

$ docker commit 1349 nontan18/texlive
sha256:a64dcff68e35d56082c52817f9432b9a2c1142d32a631f461474188912207341

これでローカルのイメージに変更が反映され,新しいイメージのSHA256ハッシュ値が出力されます.

docker images

ローカルのDockerイメージはdocker imagesコマンドで確認することができ,以下の用に,変更したDockerイメージ(nontan18/texlive)のIMAGE IDが先程のハッシュ値になっていることが確認できます.

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
nontan18/texlive                            latest              a64dcff68e35        3 minutes ago       1.48GB

実際に,docker run -it [IMAGE_NAME] [COMMAND]コマンドを用いて,更新したDockerイメージからDockerコンテナを作成すると,今度は絵文字パッケージ(fontawesome)がはじめからインストールされていることがわかります.

// コンテナを立てるとともにbashを起動してコンテナ内に入る
$ docker run -it nontan18/texlive bash
// fontawesomeがインストールされているか調べる
root@59b145c7371a:~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
root@59b145c7371a:~/work# exit
exit

※ただ,上記の方法でイメージを更新すると,イメージの中身が不透明になりメンテナンスが難しくなるのでdocker commitコマンドの仕様はあまり推奨しません.

そこで次にDockerイメージの元となるDockerfileを変更する方法を解説します.

Dockerfile

DockerfileはDockerイメージの作り方が書かれたファイルです.
今回使っているnontan18/texliveのDockerfileはgithubに公開されているので確認してみましょう.

// githubからローカルにレポジトリをクローン
$ git clone https://github.com/nontan18/texlive.git
// クローンしてきたディレクトリに移動
$ cd texlive
// ディレクトリ内のファイル一覧を出力
$ ls
Dockerfile  README.md  app  docker-compose.yml  sample
// Dockerfileの中身を出力
$ cat Dockerfile

今回使ったDockerイメージ(nontan18/texlive)の元となった以下のようなDockerfileが出力されるかと思います.

FROM ubuntu:16.04

# MAINTAINER nontan <nontan@sfc.wide.ad.jp>
LABEL maintainer="nontan@sfc.wide.ad.jp"

WORKDIR /root/work

# TexLiveの依存関係ライブラリをインストール
RUN apt-get -q update
RUN apt-get -qy install wget build-essential libfontconfig1
# RUN rm -rf /var/lib/apt/lists/*

# TexLiveのミラーサイトからパッケージをダウンロードしてきて解凍
# WARNING: このリンクは常に最新版のTexLiveが配信されるのでbuildするごとにバージョンが変わる可能性がある.
RUN wget http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz -O /tmp/install-tl-unx.tar.gz
RUN mkdir /tmp/install-tl-unx
RUN tar -xvf /tmp/install-tl-unx.tar.gz -C /tmp/install-tl-unx --strip-components=1

# インストールの設定をbasic版に変更
RUN echo "selected_scheme scheme-basic" >> /tmp/install-tl-unx/texlive.profile
# TexLiveのインストール開始
RUN /tmp/install-tl-unx/install-tl -profile /tmp/install-tl-unx/texlive.profile
# TexLiveのインストールバージョンを調べて,PATHを通せるようにシンボリックリンクを張っておく
RUN TEX_LIVE_VERSION=$(/tmp/install-tl-unx/install-tl --version | tail -n +2 | awk '{print $5}'); \
    ln -s "/usr/local/texlive/${TEX_LIVE_VERSION}" /usr/local/texlive/latest
ENV PATH="/usr/local/texlive/latest/bin/x86_64-linux:${PATH}"

# Install Packages
RUN tlmgr install latexmk
COPY ./app/config/.latexmkrc /root/.latexmkrc
# 2カラムの設定に必要なパッケージのインストール
RUN tlmgr install multirow
# 日本語対応パッケージのインストール
RUN tlmgr install collection-langjapanese
# フォントパッケージのインストール
RUN tlmgr install collection-fontsrecommended
RUN tlmgr install collection-fontutils

# References
# - https://github.com/blang/latex-docker
# - https://github.com/Paperist/docker-alpine-texlive-ja/blob/master/Dockerfile

ここで,先程のようにfontawesomeをインストールしておくには,RUN tlmgr install fontawesomeを追加します.

RUN tlmgr install collection-fontutils
# 下の行を追加
RUN tlmgr install fontawesome

docker build

DockerfileからDockerイメージをビルドするには,docker build -t [IMAGE NAME] [Dockerfileのパス]コマンドを用います.

$ docker build -t mylatexlive . 

Dockerイメージのビルドが終わったら,docker imagesコマンドでDockerイメージが追加されているのがわかります.

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
mylatexlive                                 latest              0bbb5bed8ce1        7 minutes ago       1.45GB

当然,このDockerイメージからDockerコンテナを作成すると,fontawesomeのパッケージがインストールされていることがわかります.

// コンテナを立てるとともにbashを起動してコンテナ内に入る
$ docker run -it mytexlive bash
// fontawesomeがインストールされているか調べる
root@59b145c7371a:~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
root@59b145c7371a:~/work# exit
exit

docker-composeの基本的な使い方

ここまででDockerの基本的な使い方を学びましたが,実際にDockerを用いて環境構築を行おうと考えると,複数のコンテナで様々なDockerイメージやオプションを追加しなければならなく非効率です.

そこで,複数のコンテナを効率的に管理するためにdokcer-composeを紹介します.

docker-compose.yml

docker-composeはdocker-compose.ymlに記述したDockerコンテナの構成をdocker-compose upコマンドやdocker-compose downコマンドなどで手軽に展開できるツールです.

ここでは,上の過程でgit cloneしたdocker-compose.ymlを見てみましょう.

docker-compose.yml
version: "3.1"
services:
  texlive:
    # build: .
    # image: nontan18/texlive
    image: texlive
    volumes:
      - ./sample:/root/work
    command: latexmk --pvc /root/work/sample.tex

  nginx:
    # /root/public以下のファイルをホスティングするnginxベースのイメージ
    image: nontan18/stable-file-host
    ports:
      # "ホストのポート:コンテナのポート"
      - "8080:80"
    volumes:
      # 生成されるsample.pdfを公開ディレクトリにマウント
      - ./sample/sample.pdf:/root/public/sample.pdf

上記のdocker-compose.ymlではtexliveコンテナによって出力されたsample.pdfがnginxコンテナの/root/public/sample.ymlにマウントされるようになっています.nginxコンテナのイメージであるnontan18/stable-file-host/root/public/以下のファイルをホスティングしているため,nginxコンテナの80番ポートにアクセスすることができれば,ブラウザ上でsample.pdfを閲覧することができます.

ここではportsでホストの8080番ポートをnginxコンテナ内の80番ポートに転送しているので,このコンテナがたつと,ブラウザからhttp://localhost:8080/sample.pdfにアクセスすることで出力されたPDFを閲覧することが可能になります.

docker-compose up

上記のdocker-compose.ymlのあるディレクトリでdocker-compose upコマンドを実行すると下記のようにdocker-compose.ymlに記述されたすべてのコンテナが起動します.

$ docker-compose up
Starting texlive_nginx_1 ...
Starting texlive_nginx_1 ... done
Attaching to texlive_texlive_1, texlive_nginx_1

上記の通り,http://localhost:8080/sample.pdfにアクセスすると出力されたPDFが確認できるはずです.

docker-compose ps

docker-compose upした状態でdocker-compose psコマンドを実行すると,docker-compose.ymlで定義された各コンテナが確認できます.

$ docker-compose ps
      Name                     Command               State          Ports
---------------------------------------------------------------------------------
texlive_nginx_1     nginx -g daemon off;             Up      0.0.0.0:8080->80/tcp
texlive_texlive_1   latexmk --pvc /root/work/s ...   Up

docker-compose down

docker-compose upをした状態でCtrl + Cで各コンテナを停止させることもできますが,別のターミナルのセッションからdocker-compose downを用いることで起動したすべてのDockerコンテナを停止することができます.

$ docker-compose down

docker-compose downには,マウントしたDockerボリューム(本稿では解説していませんが,ホストの特定ディレクトリをマウントする以外のコンテナのデータを永続化させる方法)をすべて削除する-vオプションや使用しているイメージを削除する--rmiオプションなどが存在します.

MySQL+Django+Angularのようなサーバークライアントモデルの開発環境を構築する際には,このdocker-composeは非常に便利です.

おわりに

最後に,今回,無造作に建てたDockerコンテナとイメージを削除してクリーンな状態に戻しましょう.
docker container rm [CONTAINER ID]コマンドやdocker image rm [IMAGE NAME]コマンドでもDockerコンテナやDockerイメージの削除は可能ですが,ここではdocker container prunedocker image pruneを紹介します.

docker container prune

docker container pruneコマンドは停止しているDockerコンテナをすべて削除するコマンドです.
docker psコマンドで起動中のコンテナを確認し,不必要なコンテナをdocker stop [CONTAINER ID]で停止させたのち,docker container pruneコマンドを実行しましょう.

$ docker container prune

docker image prune

docker container pruneと同様にdocker image pruneは起動中のコンテナに使用されていないDockerイメージを全て削除します.(なぜだかは知りませんが削除するイメージの数が多いとだいぶ長く時間がかかることがあります)

$ docker image prune

これで本稿で作成したコンテナもイメージも綺麗さっぱりホストから削除することができました.

従来の仮想化技術より軽量なコンテナ仮想化技術は,複雑な環境ごとパッケージ化したアプリケーションを気軽に展開したり削除したりできて非常に便利です.

本稿で紹介したのはDockerコマンドの一部に過ぎず,DockerはCIツールやクラスターにコンテナによるオーケストレーションを展開するkubernetes,アプリケーションコンテナの依存関係を整理しパッケージ管理するHelm,Docker環境にkubernetesクラスターを瞬時に展開しHelmを用いてアプリケーションを展開するRancherなどDockerの世界は非常に広大です.

本稿が皆様のHello Docker Worldのきっかけになれば幸いです.

注意事項

上記では何の躊躇もせずに筆者がビルドしたDockerイメージを使用していますが,信頼できない他人の作ったDockerイメージを安易に使用するのには注意が必要です.
たとえDockerfileが公開されていたとしても,Dockerレジストリに公開されているDockerイメージが悪意の無いものとは限りません.
Dockerfileを精査したのち,自身でdocker buildを行って使用することを推奨します.
特にホストのボリュームをマウントする場合や,--privilegedオプションを必要とするものには注意しましょう.

参考

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

【Docker超入門】DockerでつくるLaTeX環境

本稿に関して動作がおかしいところや不明な点,ツッコミなどがあれば適宜コメントいただけるとありがたいです.

はじめに

大学の研究会で講義を1コマ持つことになったため,弊研究会では必須なLaTeX環境をDockerで作る方法を解説することにしました.

読者としてはDockerの知識が一切ない人を想定しています.

なお,本稿ではLaTeX自体の使い方に関してはあまり触れません.

LaTeXとは?

LateXとは,Donald E. Knuth氏が開発したレイアウトシステムのTexをLeslie Lamport氏が文書作成のために改良したものだそうです.主に学術論文を書く際に使用されており,数式を綺麗にレイアウトしてくれます.

例えば,世界一美しいとされるオイラーの等式でさえ,テキストで表現すると美しくないですが,

e^{iπ} + 1 = 0

TeXを用いてコンパイルすれば,以下のように美しくレイアウトされます.

$e^{iπ} + 1 = 0$

Dockerとは?

Dockerとはコンテナ仮想化技術のプラットフォームです.

image.png

従来の仮想化技術では,ホストOS上の仮想ハードウェアや,Hypervisorを利用してゲストOSを動かしていたのに対し,DockerではDocker EngineがホストOSのカーネルの利用を上手く管理することでプロセスとユーザを隔離しているため,VMに比べて軽量な仮想化環境を実現しています.

事前準備

本稿では,このDockerを用いてLaTeXのディストリビューションであるTeXLiveの環境を行うため,まずはDockerと生成されたPDFの閲覧用にSkimをインストールします,

Dockerのインストール

下記ページからDockerHubのアカウントを作成しdmgをダウンロードしてインストールします.
MAC=> https://docs.docker.com/docker-for-mac/install/
Windows=> https://docs.docker.com/docker-for-mac/install/

Mac用Skim(軽量PDFビューワー)のインストール

下記ページよりダウンロードしてインストール
https://skim-app.sourceforge.io/

全体像を把握する

今回は下記のような構成でDockerを用いてLaTeX環境を構築します.

image.png

とりあえずDockerでLaTex環境を建ててみる

DockerとSkimのインストールが完了したら,筆者が事前に用意したTeXLiveのDockerイメージ(nontan18/texlive)を用いて以下のコマンドでLaTeX環境を実際に構築してみましょう.

// 作業用のworkディレクトリを作成し移動
$ mkdir work && cd work

workディレクトリに入ったら下記の内容のファイルを作成します.

sample.tex
\documentclass[twocolumn, a4j]{article}
\usepackage{multirow}
\usepackage{amsmath,amssymb}
\usepackage[T1]{fontenc}

\title{サンプル用のTexファイル}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\author{Nozomu Miyamoto\footnotemark[2] nontan@sfc.wide.ad.jp}
\renewcommand{\thefootnote}{\arabic{footnote}}
\date{\today}

\begin{document}

\twocolumn[
\begin{@twocolumnfalse}
  \maketitle
  \vspace{-6mm}
  \begin{abstract}
    ここに概要を書きましょう.
  \end{abstract}
  \vspace{2mm}
\end{@twocolumnfalse}
]

\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\footnotetext[2]{慶應義塾大学 村井研}
\renewcommand{\thefootnote}{\arabic{footnote}}

\section{はじめに}

   hoge hoge hoge hoge

\renewcommand{\refname}{参考文献}
\begin{thebibliography}{数字}
  \bibitem[opt]{key} 文献情報
\end{thebibliography}

\end{document}

// 生成したdocument.texをdocument.pdfにコンパイル
$ docker run -v $(pwd):/root/work -it nontan18/texlive:stable latexmk --pvc ./sample.tex

これでworkディレクトリ内にsample.pdfが出力されますので,SkimなどのPDFビューワーを使って表示してみましょう.

表示できたら,お好きなエディタを用いて,sample.texを編集してみます.

$ vim sample.tex

編集が完了すると自動でコンパルが始まり,document.pdfが更新されるかと思います.

Dockerの基本的な使い方

このセクションでは上記のDockerコマンドで一体何が起きているのかを説明します.

docker run

docker runはDockerイメージを元にDockerコンテナを起動するコマンドです.
runコマンドの第1引数で指定したコマンドが起動したコンテナ内で実行されます.

上記の

$ docker run -v $(pwd):/root/work -it nontan18/texlive:1.0.0 latexmk --pvc ./sample.tex

においてはlatexmk --pvc ./sample.texコマンドが実行されています.
(latexmk --pvc ./sample.texコマンドは任意のTexファイルを監視して更新されるたびに自動コンパイルするコマンドです.)

-tオプションでDockerイメージを参照することができます.
上記の例ではDockerHubに登録されているnontan18/texliveというイメージ(より詳細には,その中でも1.0.0タグがついているイメージ)を参照して,それを元にDockerコンテナを作成しています.
(ちなみに,このDockerHubのようなDockerImageが登録されている場所をDockerRegistryと呼びます.)

-vオプションはボリュームのマウントオプションで,DockerホストのボリュームをDokcerコンテナ内の任意の場所にマウントすることができます.上記の例では,texの作業用に作った
(-v オプションでは相対リンクでボリュームを指定できないので,pwdコマンドで現在のディレクトリを取得してそれを引数に取っています.)

docker ps

docker runコマンドで動いているDockerコンテナはdocker psコマンドで確認することができます.

$ docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                               NAMES
1349e83ba710        nontan18/texlive:1.0.0            "latexmk --pvc ./sam…"   5 seconds ago       Up 3 seconds                                            gallant_galileo

docker exec

また,docker exec -it [CONTAINER ID] [COMMAND]コマンドを使うと,起動中のコンテナ内でコマンドを実行することができます.

// $CONTAINER_IDはdocker psで取得したコンテナのIDに変更(example:1349e83ba710)
$ docker exec -it $CONTEAINER_ID sh
// ここからコンテナ内
~/work# ls
sample.aux  sample.dvi  sample.fdb_latexmk  sample.fls  sample.log  sample.pdf  sample.synctex.gz  sample.tex
// TeXLiveの絵文字パッケージが入っているか調べる
~/work# tlmgr search fontawesome
// コンテナ内にTeXLiveの絵文字パッケージをインストール
~/work# tlmgr install fontawesome
//...省略
tlmgr: package repository http://mirror.utexas.edu/ctan/systems/texlive/tlnet (verified)
[1/1, ??:??/??:??] install: fontawesome [498k]
running mktexlsr ...
done running mktexlsr.
running updmap-sys ...
done running updmap-sys.
tlmgr: package log updated: /usr/local/texlive/2019/texmf-var/web2c/tlmgr.log
// コンテナから出る
~/work# exit
exit
// ホストのシェルに戻った
$

上記の例では先程建てたコンテナ内でshコマンドを実行して,ターミナルからコンテナ内に入り,TeXLiveの絵文字パッケージ(fontawsome)が入っていないことを確認して,TeXLiveパッケージマネージャー(tlmgr)を用いて絵文字パッケージをインストールしました.

docker commit(※あんま使わない)

ただ,これだけではDockerコンテナ内での変更でしか無いため,一度Dockerコンテナを削除し,docker runコマンドで再度コンテナを建てると,絵文字パッケージはインストールされていない状態に戻ってしまいます.

このDockerコンテナの変更をDockerイメージに反映させるためにはdocker commit [CONTAINER ID] [IMAGE NAME]コマンドを用います.

$ docker commit 1349 nontan18/texlive:stable
sha256:a64dcff68e35d56082c52817f9432b9a2c1142d32a631f461474188912207341

これでローカルのイメージに変更が反映され,新しいイメージのSHA256ハッシュ値が出力されます.

docker images

ローカルのDockerイメージはdocker imagesコマンドで確認することができ,以下の用に,変更したDockerイメージ(nontan18/texlive)のIMAGE IDが先程のハッシュ値になっていることが確認できます.

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
nontan18/texlive                            stable              a64dcff68e35        3 minutes ago       1.48GB

実際に,docker run -it [IMAGE_NAME] [COMMAND]コマンドを用いて,更新したDockerイメージからDockerコンテナを作成すると,今度は絵文字パッケージ(fontawesome)がはじめからインストールされていることがわかります.

// コンテナを立てるとともにshを起動してコンテナ内に入る
$ docker run -it nontan18/texlive:stable sh
// fontawesomeがインストールされているか調べる
~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
~/work# exit
exit

※ただ,上記の方法でイメージを更新すると,イメージの中身が不透明になりメンテナンスが難しくなるのでdocker commitコマンドの仕様はあまり推奨しません.

そこで次にDockerイメージの元となるDockerfileを変更する方法を解説します.

Dockerfile

DockerfileはDockerイメージの作り方が書かれたファイルです.
今回使っているnontan18/texliveのDockerfileはgithubに公開されているので確認してみましょう.

// githubからローカルにレポジトリをクローン
$ git clone https://github.com/nontan18/texlive.git
// クローンしてきたディレクトリに移動
$ cd texlive
// ディレクトリ内のファイル一覧を出力
$ ls
Dockerfile  README.md  app  docker-compose.yml  sample
// Dockerfileの中身を出力
$ cat Dockerfile

今回使ったDockerイメージ(nontan18/texlive)の元となった以下のようなDockerfileが出力されるかと思います.

# Alpine Linux v3.9.4をベースのイメージとして指定
FROM alpine:3.9.4

# MAINTAINER nontan <nontan@sfc.wide.ad.jp>
LABEL maintainer="nontan@sfc.wide.ad.jp"

WORKDIR /root/work

# パッケージリストの更新
RUN apk update
# TeXLiveのインストールに必要なパッケージを取得
RUN apk add perl wget fontconfig-dev
# TeXLiveのインストーラーの解凍に必要なパッケージのインストール
RUN apk add xz tar

# 圧縮されたTeXLiveのインストーラーをダウンロードし,tmpディレクトリに保存
ADD http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz /tmp/install-tl-unx.tar.gz
# インストーラーを解凍した際に配置するディレクトリを作成
RUN mkdir /tmp/install-tl-unx
# ダウンロードしたinstall-tl-unx.tar.gzを解凍
RUN tar -xvf /tmp/install-tl-unx.tar.gz -C /tmp/install-tl-unx --strip-components=1
# TeXLiveのインストール用の設定ファイルを作成
RUN echo "selected_scheme scheme-basic" >> /tmp/install-tl-unx/texlive.profile
# TeXLiveのインストール
RUN /tmp/install-tl-unx/install-tl -profile /tmp/install-tl-unx/texlive.profile
# TeXLiveのバージョンを取得しインストールディレクトリを特定し,latestの名称でシンボリックリンクを作成
RUN TEX_LIVE_VERSION=$(/tmp/install-tl-unx/install-tl --version | tail -n +2 | awk '{print $5}'); \
    ln -s "/usr/local/texlive/${TEX_LIVE_VERSION}" /usr/local/texlive/latest
# インストールしたTeXLiveへパスを通す
ENV PATH="/usr/local/texlive/latest/bin/x86_64-linuxmusl:${PATH}"

# TeXLive Package Managerを使用して必要なパッケージをインストール
# texファイルの自動コンパイルパッケージをインストール
RUN tlmgr install latexmk
# latexmkの設定ファイルをホストからイメージにコピー
COPY ./app/config/.latexmkrc /root/.latexmkrc
# 2カラムの設定に必要なパッケージのインストール
RUN tlmgr install multirow
# 日本語対応パッケージのインストール
RUN tlmgr install collection-langjapanese
# フォントパッケージのインストール
RUN tlmgr install collection-fontsrecommended
RUN tlmgr install collection-fontutils

# 不要なパッケージなどの削除(イメージの容量削減のため)
RUN apk del xz tar
RUN rm -rf /var/cache/apk/*
RUN rm -rf /tmp/*


# References
# - https://github.com/blang/latex-docker
# - https://github.com/Paperist/docker-alpine-texlive-ja/blob/master/Dockerfile

ここで,先程のようにfontawesomeをインストールしておくには,RUN tlmgr install fontawesomeを追加します.

# フォントパッケージのインストール
RUN tlmgr install collection-fontsrecommended
RUN tlmgr install collection-fontutils
# 下の行を追加
RUN tlmgr install fontawesome

docker build

DockerfileからDockerイメージをビルドするには,docker build -t [IMAGE NAME] .コマンドを用います.

$ docker build -t mylatexlive . 

Dockerイメージのビルドが終わったら,docker imagesコマンドでDockerイメージが追加されているのがわかります.

$ docker images
REPOSITORY                                  TAG                 IMAGE ID            CREATED             SIZE
mylatexlive                                 latest              0bbb5bed8ce1        7 minutes ago       1.45GB

当然,このDockerイメージからDockerコンテナを作成すると,fontawesomeのパッケージがインストールされていることがわかります.

// コンテナを立てるとともにshを起動してコンテナ内に入る
$ docker run -it mytexlive sh
// fontawesomeがインストールされているか調べる
~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
~/work# exit
exit

docker-composeの基本的な使い方

ここまででDockerの基本的な使い方を学びましたが,実際にDockerを用いて環境構築を行おうと考えると,複数のコンテナで様々なDockerイメージやオプションを追加しなければならなく非効率です.

そこで,複数のコンテナを効率的に管理するためにdokcer-composeを紹介します.

docker-compose.yml

docker-composeはdocker-compose.ymlに記述したDockerコンテナの構成をdocker-compose upコマンドやdocker-compose downコマンドなどで手軽に展開できるツールです.

ここでは,上の過程でgit cloneしたdocker-compose.ymlを見てみましょう.

docker-compose.yml
version: "3.1"
services:
  texlive:
    build: .
    # image: nontan18/texlive
    volumes:
      - ./sample:/root/work
    command: latexmk --pvc /root/work/sample.tex

  nginx:
    # /root/public以下のファイルをホスティングするnginxベースのイメージ
    image: nontan18/stable-file-host
    ports:
      # "ホストのポート:コンテナのポート"
      - "8080:80"
    volumes:
      # 生成されるsample.pdfを公開ディレクトリにマウント
      - ./sample:/root/public

上記のdocker-compose.ymlではtexliveコンテナでコンパイルされたsample.pdfファイルが入っているsampleディレクトリがnginxコンテナの/root/public/にマウントされるようになっています.nginxコンテナのイメージであるnontan18/stable-file-host/root/public/以下のファイルをホスティングしているため,nginxコンテナの80番ポートにアクセスすることができれば,ブラウザ上でsample.pdfを閲覧することができます.

ここではportsでホストの8080番ポートをnginxコンテナ内の80番ポートに転送しているので,このコンテナがたつと,ブラウザからhttp://localhost:8080/sample.pdfにアクセスすることで出力されたPDFを閲覧することが可能になります.

docker-compose up

上記のdocker-compose.ymlのあるディレクトリでdocker-compose upコマンドを実行すると下記のようにdocker-compose.ymlに記述されたすべてのコンテナが起動します.

$ docker-compose up
Starting texlive_nginx_1 ...
Starting texlive_nginx_1 ... done
Attaching to texlive_texlive_1, texlive_nginx_1

上記の通り,http://localhost:8080/sample.pdfにアクセスすると出力されたPDFが確認できるはずです.

docker-compose ps

docker-compose upした状態でdocker-compose psコマンドを実行すると,docker-compose.ymlで定義された各コンテナが確認できます.

$ docker-compose ps
      Name                     Command               State          Ports
---------------------------------------------------------------------------------
texlive_nginx_1     nginx -g daemon off;             Up      0.0.0.0:8080->80/tcp
texlive_texlive_1   latexmk --pvc /root/work/s ...   Up

docker-compose down

docker-compose upをした状態でCtrl + Cで各コンテナを停止させることもできますが,別のターミナルのセッションからdocker-compose downを用いることで起動したすべてのDockerコンテナを停止することができます.

$ docker-compose down

docker-compose downには,マウントしたDockerボリューム(本稿では解説していませんが,ホストの特定ディレクトリをマウントする以外のコンテナのデータを永続化させる方法)をすべて削除する-vオプションや使用しているイメージを削除する--rmiオプションなどが存在します.

MySQL+Django+Angularのようなサーバークライアントモデルの開発環境を構築する際には,このdocker-composeは非常に便利です.

おわりに

最後に,今回,無造作に建てたDockerコンテナとイメージを削除してクリーンな状態に戻しましょう.
docker container rm [CONTAINER ID]コマンドやdocker image rm [IMAGE NAME]コマンドでもDockerコンテナやDockerイメージの削除は可能ですが,ここではdocker container prunedocker image pruneを紹介します.

docker container prune

docker container pruneコマンドは停止しているDockerコンテナをすべて削除するコマンドです.
docker psコマンドで起動中のコンテナを確認し,不必要なコンテナをdocker stop [CONTAINER ID]で停止させたのち,docker container pruneコマンドを実行しましょう.

$ docker container prune

docker image prune

docker container pruneと同様にdocker image pruneは起動中のコンテナに使用されていないDockerイメージを全て削除します.(なぜだかは知りませんが削除するイメージの数が多いとだいぶ長く時間がかかることがあります)

$ docker image prune

これで本稿で作成したコンテナもイメージも綺麗さっぱりホストから削除することができました.

従来の仮想化技術より軽量なコンテナ仮想化技術は,複雑な環境ごとパッケージ化したアプリケーションを気軽に展開したり削除したりできて非常に便利です.

本稿で紹介したのはDockerコマンドの一部に過ぎず,DockerはCIツールやクラスターにコンテナによるオーケストレーションを展開するkubernetes,アプリケーションコンテナの依存関係を整理しパッケージ管理するHelm,Docker環境にkubernetesクラスターを瞬時に展開しHelmを用いてアプリケーションを展開するRancherなどDockerの世界は非常に広大です.

本稿が皆様のHello Docker Worldのきっかけになれば幸いです.

注意事項

上記では何の躊躇もせずに筆者がビルドしたDockerイメージを使用していますが,信頼できない他人の作ったDockerイメージを安易に使用するのには注意が必要です.
たとえDockerfileが公開されていたとしても,Dockerレジストリに公開されているDockerイメージが悪意の無いものとは限りません.
Dockerfileを精査したのち,自身でdocker buildを行って使用することを推奨します.
特にホストのボリュームをマウントする場合や,--privilegedオプションを必要とするものには注意しましょう.

参考

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

Laravelローカル開発環境10分でセットアップ手順(Laradock)

Webアプリケーション開発で、Laravel利用、スピード重視な方、バックエンドメインでインフラ経験がない方向けの記事になります。

触れる技術、サービスのリストはこちら
- Laravel
- Laradock
- Docker for Mac
- docker-compose

ローカル開発環境設定

Laravel

まずはローカルでLaravelアプリケーションの作成。

% composer create-project --prefer-dist laravel/laravel yourappname 

または

% laravel new yourappname 

Laradock

次にLaradockでLaravel用Docker環境を作ります。
*Docker for Macがない方は、ここでインストールしておいてください。
*docker-composeコマンドがない方は、ここでインストールしておいてください。

Laradock(laravel用docker環境)をローカルに落としてきます。
自分はアプリケーション作成したディレクトリと同じ階層にcloneしています。

% git clone https://github.com/Laradock/laradock.git
% ls
yourappname laradock

という感じで。次はlaradockとアプリケーションをつないでいきます。

% cd laradock
% cp env-example .env

.env設定@laradock

.envを編集

.env

APP_CODE_PATH_HOST=../yourappname
DATA_PATH_HOST=.laradock/yourappname_data

# MYSQLの項目を探す

MYSQL_VERSION=5.7 # 推奨
MYSQL_DATABASE=任意
MYSQL_USER=任意
MYSQL_PASSWORD=任意

nginx設定@laradock

laradock/nginx/sites/の下にアプリケーション用の設定ファイル(下記ではyourapp.conf)を作成します。

yourapp.conf

server {

    listen 80;
    #listen [::]:80;

    # For https
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server ipv6only=on;
    # ssl_certificate /etc/nginx/ssl/default.crt;
    # ssl_certificate_key /etc/nginx/ssl/default.key;

    server_name yourapp.test;
    root /var/www/yourappname/public;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_pass php-upstream;
        fastcgi_index index.php;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        #fixes timeouts
        fastcgi_read_timeout 600;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/;
        log_not_found off;
    }
}

後少しです。

.env@laravel

今度は、yourappnameのディレクトリに移動して、アプリケーションの.envファイルを修正します

# .env
DB_HOST=mysql
REDIS_HOST=redis
QUEUE_HOST=beanstalkd

hostsファイル編集

今、nginxの項で設定したyourapp.testにアクセスできないのでアクセスできるようにします(hostsファイルをいじります)

% sudo vim /etc/hosts

127.0.0.1 yourapp.test #追加

Dockerコンテナ立ち上げ

いよいよです。
Dockerが立ち上がっていることを確認して、

% docker-compose up nginx mysql php-fpm redis

でコンテナを立ち上げます

ブラウザでyourapp.testにアクセスできることを確認。
php artisan migrateする時は、workspaceコンテナに入ります。

# laradock
% docker-compose exec --user=laradock workspace bash
laradock####:/var/www$ cd yourappname
laradock####:/var/www$ php artisan migrate

簡単にローカル開発環境がセットアップできました。

続き

次は下記に関して書いて、通しでローカル開発環境、ステージング環境を作れるよう記事を書きたいと思います。

laravelステージング環境設定について
利用するサービスリスト
- Amazon Web Service
- Elasticbeanstalk
- Amazon RDS

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