20200205のdockerに関する記事は12件です。

docker入門メモ

dockerとは

開発環境をコンテナという単位で作るシステム。dockerhubというところにあげて色んな所に開発環境をコピーすることでどの端末でも同じ開発環境で開発できるようになる。

docker全体像

  • dockerfileからimageを作成
  • imageは設計書のようなものでこれからcontainerを作成
  • このcontainer上でアプリを動かす
  • imageはdockerhubで共有できる

コンテナ管理

Kubernetesとdockerswarmの違い

「コンテナ管理ツールのdocekr swarm/Kubernetesについて調べてみた」
https://go-mount.hatenablog.com/entry/2018/09/27/220404

この記事がわかりやすかった。

・webサービスを提供するにしても、フロントエンドのWebサーバから、webサーバからの処理を返すアプリケーション、データベースなど、複数のアプリケーションが必要。それぞれアプリごとにコンテナを作って、複数コンテナを連携させたい。
・安定してサービス提供を行うため、データベースはMaster/Slaveの冗長構成で、別々のサーバで、別々にコンテナを動かして連携させたい。
・新しいコンテナを立ち上げるときは、リソースが比較的空いてるサーバに立ち上げてほしい
・コンテナがちゃんと起動しているか確認して、停止していたら再起動してほしい

上記の目的で使うもの。Kubernetesのほうが下記で優れているらしい

docker swarmの時と同じように、管理機能を持つmaster nodeと実際にコンテナを実行させるnodeに分かれています。docker swarmとさらに違う点としては、複数コンテナを集めてPodという単位でkubernetes上ではコンテナを管理します。

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

【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント4つ

はじめに

Docker環境下でこんなシーンに遭遇したことはありませんか?

「Railsでバグ発生!デバッグしなきゃ!よーし、binding.pryしよう!」

「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」

こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1
Docker: 19.03.5
docker-compose: 1.24.1

1.binding.pryのコンソールが出ない

docker-compose upなどでrails serverを立ち上げていて、binding.pryを入力した箇所で動作が止まっているのに、

「コンソールが出ない!どうしよう!」

という状態を想定しています。

解決法

$docker container ls

でRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1とします。

$ docker attach rails_app_web_1

docker attachで該当コンテナにattachします。
これで、binding.pryしたときにコンソールが表示されます。

※もし反応がなければ、Enter押下するとコンソールが出るかもです。

2.せっかくコンソールが出たのに終了の仕方がわからない

多くの場合はbinding.pryを一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。

下手にCtrl + Cで終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。

解決法

pryの画面から終了するならcontinueと入力してrails serverを通常動作に戻し、docker attachを維持するのが便利です。

この状態であれば、次にbinding.pryしたときもスムーズにデバッグ作業が可能です。

※デバッグが終了してコンテナから抜けたい場合、Ctrl + P + Q(Macの場合)でコンテナを停止せずに抜けられます。

3.pryで日本語入力出来ない

少し外れますが、そもそもrails consolepryを使っていて、日本語が入力できないパターンもあるかもしれません。

解決法

Dockerfile
ENV LANG C.UTF-8

Dockerfileに上記のように追記すれば解決できます。

これを忘れるとpry日本語入力が効きません。

4.コンテナがすぐ落ちる、コンソールに文字が入力できない

2020/2/6追記

解決法

docker-compose.yml
web:
  tty: true
  stdin_open: true

上記がdocker-compose.ymlのRailsに関係する箇所に書かれているかどうか確認します。(今回はwebとしています。)

  • tty: true ポート待受などをしていないコンテナを起動させ続けるオプション
  • stdin_open: true 標準入力出来るようになるオプション

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

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

【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント3つ

はじめに

Docker環境下でこんなシーンに遭遇したことはありませんか?

「Railsでバグ発生!デバッグしなきゃ!よーし、binding.pryしよう!」

「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」

こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1
Docker: 19.03.5
docker-compose: 1.24.1

1.binding.pryのコンソールが出ない

docker-compose upなどでrails serverを立ち上げていて、binding.pryを入力した箇所で動作が止まっているのに、

「コンソールが出ない!どうしよう!」

という状態を想定しています。

解決法

$docker container ls

でRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1とします。

$ docker attach rails_app_web_1

docker attachで該当コンテナにattachします。
これで、binding.pryしたときにコンソールが表示されます。

※もし反応がなければ、Enter押下するとコンソールが出るかもです。

2.せっかくコンソールが出たのに終了の仕方がわからない

多くの場合はbinding.pryを一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。

下手にCtrl + Cで終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。

解決法

pryの画面から終了するならcontinueと入力してrails serverを通常動作に戻し、docker attachを維持するのが便利です。

この状態であれば、次にbinding.pryしたときもスムーズにデバッグ作業が可能です。

※デバッグが終了してコンテナから抜けたい場合、Ctrl + P + Q(Macの場合)でコンテナを停止せずに抜けられます。

3.pryで日本語入力出来ない

少し外れますが、そもそもrails consolepryを使っていて、日本語が入力できないパターンもあるかもしれません。

解決法

Dockerfile
ENV LANG C.UTF-8

Dockerfileに上記のように追記すれば解決できます。

これを忘れるとpry日本語入力が効きません。

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

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

Dockerコマンド、インストール

書いてあること

  • Dockerインストール、コマンドのメモ

よく使うコマンド

bash
#バージョン確認
$ docker version
$ docker-compose version
$ docker-machine version

#停止中のコンテナ、使っていないイメージやネットワークをまとめて削除
$ docker system prune

#仮想マシンの確認
$ docker-machine ls

#仮想マシンを起動
$ docker-machine start

#仮想マシンを停止
$ docker-machine stop

#イメージの確認
$ docker images

#イメージの検索
$ docker search centos

#DockerHubからイメージをダウンロード
$ docker pull centos:7

#イメージ削除
$ docker rmi centos:7

#不要なイメージをすべて削除
$ docker image prune

#使用していないイメージをすべて削除
# docker image prune -a

#コンテナの確認
$ docker ps

#コンテナの確認(すべて)
$ docker ps -a

#コンテナ作成
#-d:バックグラウンド実行、-p:「ホスト:コンテナ」でポート転送、--name:コンテナ名を設定
$ docker run -d -p 8080:80 --name nginx nginx

#コンテナ作成
#-it:ホストとコンテナの標準入出力をつなげる
$ docker run -it -d -p 8080:80 --name centos centos:7

#コンテナ起動
$ docker start nginx

#コンテナ停止
$ docker stop nginx

#コンテナ削除
$ docker rm centos

#コンテナに接続(ログイン)
#新たにbashプロセスを作成して接続する
$ docker exec -it centos bash

#コンテナの接続(ログイン)
#標準入出力に接続しているため、exitでログアウトするとコンテナ自体が停止する
$ docker attach centos

#コンテナからイメージを新たに作成
$ docker commit centos original-nginx

#コンテナ起動と同時にログインし、ログアウトでコンテナ削除
#Dockerfile作成時など、コマンド実行とDockerfile記載を繰り返す際に便利
$ docker run -it -p 8080:80 --rm --name centos centos:7 bash

#Dockerfileからイメージ作成(ビルド)
#-t:イメージ名、.:Dockerfileのパス
#イメージ名のタグを省略するとlatestとなる
$ docker build -t original-nginx .

#Dockerfileから作成したイメージでコンテナ起動
$ docker run -it -d -p 8080:80 --name build-nginx original-nginx

#ネットワークを確認
$ docker network ls

#ネットワークを作成
$ dockernetwork create test-network

#Docker Composeビルド
$ docker-compose build

#Docker Composeビルド
#--no-cache:キャッシュを使用しない、ビルド対象をappサービスのみとする
$ docker-compose build app --no-cache

#Docker Composeコンテナ作成・起動
#-d:バックグラウンド実行
$ docker-compose up -d

#Docker Compose appサービスからコンテナ作成・起動
$ docker-compose up -d app

#コンテナの起動ログを出力
$ docker-compose logs

#Docker Compose appサービスから新たなコンテナを作成してコマンド実行
#コマンドを実行した回数だけコンテナが増えていく
$ docker-compose run --rm app rails db:create

#Docker Compose 既存のappコンテナを起動してコマンド実行
$ docker-compose exec app bash

#Docker Compose サービス内のコンテナを起動
$ docker-compose start

#Docker Compose サービス内のコンテナを停止
$ docker-compose stop

#Docker Compose サービス内のコンテナを削除
$ docker-compose rm

#Docker Compose appサービスのコンテナのみ削除
$ docker-compose rm app

#Docker Composeサービス内のコンテナを停止し、ネットワークごとコンテナを削除
$ docker-compose down

#Docker Composeサービス内のコンテナを停止し、ネットワークごとコンテナ・イメージを削除
$ docker-compose down --rmi all

インストール

MacOS

下記Webページを開く

Docker Desktop for Mac - Docker Hub

Please Login To Downloadをクリック

image.png

Docker IDでログイン(IDがない場合はSign Up)

image.png

ダウンロードしたDocker.dmgを実行し、画面にそってインストールを進める

image.png

OKをクリックし、パスワードを入力する

image.png

Docker Desctop is runningとなったら正常起動

image.png

Windows

Hyper-Vを有効化

20200129_001.png

下記Webページを開く

Docker Desktop for Windows - Docker Hub

Please Login To Downloadをクリック

20200129_002.png

Docker IDでログイン(IDがない場合はSign Up)

image.png

ダウンロードしたDocker Desktop Installer.exeを実行し、画面にそってインストールを進める

以降はMacと同様の手順

Docker

bash
#Dockerのバージョンを確認
$ docker version

#Docker実行環境確認
$ docker system info

#Dockerのディスク利用状況を確認
$ docker system df

#不要なイメージ・コンテナを一括削除
$ docker system prune -a

Docker Hub

bash
#Docker Hubへログイン
$ docker login

#ユーザー名、パスワードを指定してDocker Hubへログイン
$ docker login -u ユーザー名 -p パスワード

#Docker Hubからログアウト
$ docker logout

Docker Image

bash
#イメージを一覧表示
$ docker image ls

#イメージを詳細確認
$ docker image inspect イメージ名[:タグ名]

#イメージの詳細情報からContainerConfig.Imageを取得
$ docker image inspect --format="{{.ContainerConfig.Image}}" イメージ名[:タグ名]

#イメージをダウンロード。タグ名を指定しない場合は最新版(latest)が取得される
$ docker image pull イメージ名[:タグ名]

#すべてのタグのイメージをダウンロード
$ docker image pull -a イメージ名

#URLを指定してイメージをダウンロード。URLはプロトコル(https://)を除いて指定
$ docker image pull [URL]

#イメージのなりすまし・改ざん防止機能(Docker Content Trust)を有効化・無効化
$ export DOCKER_CONTENT_TRUST=1
$ export DOCKER_CONTENT_TRUST=0

#イメージを削除
$ docker image rm イメージ名[:タグ名]

#イメージを強制削除
$ docker image rm -f イメージ名[:タグ名]

#未使用のイメージを強制削除
$ docker image prune -a

#イメージのタグ名を設定。設定するタグ名は「ユーザーID/イメージ名:タグ名」で指定
$ docker image tag イメージ名[:タグ名] 設定するタグ名

#イメージをアップロード
$ docker image push イメージ名[:タグ名]

#イメージをキーワードで検索
$ docker search 検索キーワード

#お気に入り1000件以上をイメージをキーワード検索
$ docker search --filter=stars=1000 検索キーワード

#tarファイルからイメージを作成
$ docker image import ファイルまたはURL - [イメージ名[:タグ名]]

#test.tarのディレクトリ・ファイルから「user/test:1.1」イメージを作成
$ cat test.tar | docker image import - user/test:1.1

#イメージをtarファイルへ保存
$ docker image save [オプション] 保存ファイル名 [イメージ名]

#testイメージをtest.tarファイルへ保存
$ docker image save -o test.tar test

#tarファイルからイメージの読み込み
$ docker image load [オプション]

#test.tarファイルからイメージの読み込み
$ docker image load -i test.tar

Docker Container

bash
#稼働中のコンテナを一覧表示
$ docker container ls [オプション]

#稼働中/停止中のコンテナを一覧表示
$ docker container ls -a

#コンテナ名がtestのコンテナを表示
$ docker container ls -a -f name=test

#コンテナの終了コードが0のコンテナを表示
$ docker container ls -a -f exited=0

#コンテナ名と状態をコロンで区切って表示
$ docker container ls -a --format "{{.Names}}: {{.Status}}"

#コンテナ名、状態、ボリュームマウントを表形式で表示
$ docker container ls -a --format "table {{.Names}}\t{{.Status}}\t{{.Mounts}}"

#コンテナの稼働状態を確認
$ docker container status [コンテナ名]

#コンテナで稼働しているプロセスを確認
$ docker container top [コンテナ名]

#コンテナを生成・起動
$ docker container run [オプション] イメージ名[:タグ名] [引数]

#CentOSイメージから「test」というコンテナ名でコンテナを起動し、シェルを実行
$ docker container run -it --name "test" centos /bin/bash

#CentOSイメージからコンテナを起動し、`/bin/cal`コマンドを実行・結果をコンソール表示後、コンテナを自動削除
$ docker container run --rm -it centos /bin/cal

#CentOSイメージからコンテナをバックグラウンド起動し、pingコマンドを実行
#バックグラウンド起動時はコンテナIDのみターミナルに表示されるため、logsオプションで結果を確認
$ docker container run -d centos /bin/ping localhost
$ docker container logs -t コンテナ識別子

#CentOSイメージからコンテナを起動し、ホスト名「test.com」を指定、/etc/hostsに「node1.test.com<=>192.168.2.10」の定義情報を追加
$ docker container run -it -h test.com --add-host node1.test.com:192.168.2.10 centos

#Nginxイメージからコンテナをバックグラウンド起動し、ホスト8080番ポートをコンテナ80番ポートへマッピング
$ docker container run -d -p 8080:80 nginx

#Nginxイメージからコンテナをバックグラウンド起動し、DNSサーバー・MACアドレスを指定
$ docker container run -d --dns 192.168.2.1 --mac-address="xx:xx:xx:xx:xx:xx" nginx

#CentOSイメージからコンテナを起動し、メモリを1GBを指定、ホストの/Users/●●●/shareとコンテナの/user/share/nginx/htmlを共有
$ docker container run -m 1g -v /Users/●●●/share:/user/share/nginx/html centos

#CentOSイメージからコンテナを起動し、環境変数を設定。setで環境変数が定義されていることを確認
$ docker container run -it -e foo=bar centos /bin/bash
$ set

#CentOSイメージからコンテナを起動し、作業ディレクトリを指定
$ docker container run -it -w=/testdir centos /bin/bash

#コンテナを起動
$ docker container start [オプション] コンテナ識別子 [コンテナ識別子]

#コンテナを停止
$ docker container stop [オプション] コンテナ識別子 [コンテナ識別子]

#コンテナを再起動
$ docker container restart [オプション] コンテナ識別子 [コンテナ識別子]

#コンテナを削除
$ docker container rm [オプション] コンテナ識別子 [コンテナ識別子]

#停止中のコンテナを削除
$ docker container prune

#コンテナを中断
$ docker container pause コンテナ識別子

#コンテナを再開
$ docker container unpause コンテナ識別子

#コンテナへ接続
#Ctrl+Cでデタッチ・コンテナ終了、Ctrl+Qでデタッチのみ
$ docker container attach コンテナ識別子

#稼働中のコンテナでプロセスを実行
$ docker container exec [オプション] コンテナ識別子 実行するコマンド 引数]

#稼働中の「test」コンテナで/bin/bashを実行
$ docker container exec -it test /bin/bash

#稼働中のコンテナのポート転送確認
$ docker container port コンテナ識別子

#コンテナの名前変更
$ docker container rename 変更前コンテナ名 変更後コンテナ名

#コンテナ内のファイルをコピー
$ docker container cp コンテナ識別子:コンテナ内のファイルパス ホストのディレクトリパス
$ docker container cp ホストのディレクトリパス コンテナ識別子:コンテナ内のファイルパス

#コンテナ操作の差分確認
$ docker container diff コンテナ識別子

#コンテナからイメージを作成
$ docker container commit [オプション] コンテナ識別子 [イメージ名[:タグ名]]

#testコンテナから作成者が「User」、イメージ名が「user/test:1.0」のイメージを作成
$ docker container commit -a "User" test user/test:1.0

#コンテナをtarファイル出力。docker image importでtarファイルからイメージを作成する
$ docker container export コンテナ識別子

#testコンテナからtest.tarファイル出力
$ docker container export test > test.tar

Docker Network

bash
#ネットワークを一覧表示
$ docker network ls [オプション]

#ブリッジネットワークのネットワークIDを一覧表示
$ docker network ls -q -f driver=bridge

#ネットワークの詳細確認
$ docker network inspect [オプション] ネットワーク

#web-networkネットワークの詳細確認
$ docker network inspect web-network

#ネットワークを作成
$ docker network create [オプション] ネットワーク

#web-networkというブリッジネットワークを作成
$ docker network create -d bridge web-network

#ネットワークを削除
$ docker network rm [オプション] ネットワーク

#web-networkネットワークを削除
$ docker network rm web-network

#コンテナをネットワークへ接続
$ docker network connect [オプション] ネットワーク コンテナ

#testコンテナをweb-networkネットワークへ接続
$ docker network connect web-network test

#ネットワークを指定してコンテナを起動。ネットワークを明示的に指定しない場合、デフォルトのブリッジネットワークに接続される
$ docker container run -itd --name=コンテナ識別子 --net=ネットワーク イメージ名[:タグ名]

#CentOSイメージからコンテナを起動し、web-networkへ接続後、コンテナの詳細確認
$ docker container run --name="test" --net=web-network centos
$ docker container inspect test

#コンテナをネットワークから切断
$ docker network disconnect [オプション] ネットワーク コンテナ

#testコンテナをweb-networkネットワークから切断
$ docker network disconnect web-network test

Dockerfile

Dockerfileの命令

命令 説明
FROM ベースイメージの指定
RUN コマンド実行
CMD コンテナの実行コマンド
LABEL ラベルを設定
EXPOSE ポートのエクスポート
ENV 環境変数
ADD ファイル/ディレクトリの追加
COPY ファイルのコピー
ENTRYPOINT コンテナの実行コマンド
VOLUME ボリュームのマウント
USER ユーザーの指定
WORKDIR 作業ディレクトリ
ARG Dockerfile内の変数
ONBUILD ビルド完了後に実行されるコマンド
STOPSIGNAL システムコールシグナルの設定
HEALTHCHECK コンテナのヘルスチェック
SHELL デフォルトシェルの設定
bash
#DockerfileからDockerimeイメージを作成。Dockerfileの場所は絶対パス/相対パスどちらも利用可能
$ docker build -t [生成するイメージ名]:[タグ名] [Dockerfileの場所]

#/home/docker/testディレクトリのDockerfileからtest:1.0イメージを作成
$ docker build -t test:1.0 /home/docker/test

#カレントディレクトリのDocker.baseファイルからtest:2.0イメージを作成
$ docker build -t test:2.0 -f Docker.base .

#イメージ生成の過程で実行されたコマンドを確認
$ docker history イメージ名
#DockerコンテナをどのDockerイメージから生成するか
FROM [イメージ名]
FROM [イメージ名]:[タグ名]
FROM [イメージ名]@[ダイジェスト]

#イメージを作成するためのコマンド実行
RUN [実行したいコマンド]

#Shell形式でNginxのインストール。/bin/shで実行される
RUN apt-get install -y nginx

#Exec形式でNginxのインストール。シェルを介さず実行される
RUN ["apt-get install -y nginx"]

#Exec形式でbash指定でNginxのインストール
RUN ["/bin/bash", "-c", "apt-get install -y nginx"]

#コンテナ内でのコマンド実行
CMD [実行したいコマンド]

#Exec形式でNginxをフォアグラウンド実行
CMD ["nginx", "-g", "daemon off;"]

#Shell形式でNginxをフォアグラウンド実行
CMD nginx -g 'daemon off;'

#ポート指定
EXPOSE ポート番号

#コンテナの実行コマンド
ENTRYPOINT [実行したいコマンド]

#Exec形式でNginxをフォアグラウンド実行
ENTRYPOINT ["nginx", "-g", "daemon off;"]

#Shell形式でNginxをフォアグラウンド実行
ENTRYPOINT nginx -g 'daemon off;'

#ビルド完了後に実行するコマンド
#※自身のDockerfileから生成したイメージをベースイメージとした別のDockerfileをビルドする際に実行するコマンドを指定
ONBUILD [実行したいコマンド]

#システムコールシグナルの設定。シグナル番号(9など)またはシグナル名(SIGKILLなど)を指定
STOPSIGNAL [シグナル]

#コンテナのヘルスチェック命令
HEALTHCHECK [オプション] CMD 実行するコマンド

#5分毎に稼働中のWebサーバーのメインページ(https://localhost/)を3秒以内に表示されるか確認。ヘルスチェック結果は`docker container inspect`コマンドで確認
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f https://localhost/ || exit 1

#環境変数の設定
ENV [key]=[value]

#複数の環境変数を一度に設定
ENV myName="yoshi0518" myAge=34

#作業ディレクトリの指定
#WORKDIRはRUN、CMD、ENTRYPOINT、COPY、ADDコマンドで利用される
WORKDIR [作業ディレクトリのパス]

#絶対パス/相対パスで作業ディレクトリを指定
WORKDIR /first
WORKDIR second
WORKDIR third
RUN ["pwd"] #/first/second/third

#環境変数を利用して作業ディレクトリを指定
ENV DIRPATH /first
ENV DIRNAME second
WORKDIR $DIRPATH/$DIRNAME
RUN ["pwd"] #/first/second

#ユーザーの指定
#USERはRUN、CMD、ENTRYPOINTコマンドで利用される
USER [ユーザー名/UID]

#ユーザーをyoshi0518に指定
USER yoshi0518

#ラベルの指定
#イメージにバージョン情報や作成者情報などを持たせる際に利用
LABEL [key]=[value]

#作成者、バージョン情報を設定。確認はdocker image inspectで確認
LABEL author="yoshi0518"
LABEL version="1.0.0"

#ポートの設定
EXPOSE ポート番号

#Dockerfile内変数の設定
ARG 変数名[=デフォルト値]

#YOURNAME変数を定義
ARG YOURNAME="yoshi0518"

#デフォルトのシェルを設定
SHELL ["シェルのパス", "パラメータ"]

#bashをデフォルトシェルに設定
SHELL ["/bin/bash", "-c"]

#ファイル/ディレクトリの追加
ADD ホストのファイルパス Dockerイメージのファイルパス
ADD ["ホストのファイルパス" "Dockerイメージのファイルパス"]

#ホスト上のindex.htmlをイメージの/test_dir/httpdocs/に追加。絶対パスでコピー先を指定
ADD index.html /test_dir/httpdocs/

#ホスト上のindex.htmlをイメージの/test_dir/httpdocs/に追加。WORKDIRで起点となるディレクトリを指定
WORKDIR /test_dir
ADD index.html httpdocs/

#ワイルドカードで複数のファイルを追加。*は任意の文字、?は任意の1文字
ADD test* /test/dir/

#URLで指定したリモートファイルを追加。パーミッションは600(ユーザーのみ読み書き可能)となる。認証はできないため、リモートファイルダウンロードに認証が必要な場合はRUNでwgetやcurlコマンドを実行する
ADD https://sample.com/index.html /test_dir/httpdocs

#ファイルをコピー
COPY ホストのファイルパス Dockerイメージのファイルパス
COPY ["ホストのファイルパス" "Dockerイメージのファイルパス"]

#ボリュームのマウント
VOLUME ["/マウントポイント"]

CMDENTRYPOINTの違い

CMD:コンテナ起動時に実行したいコマンドを定義しても、docker container runコマンド実行時に引数で荒玉コマンドを指定した場合、そちらを優先実行する
ENTRYPOINT:必ずコンテナで実行されるが、実行時にコマンド引数を指定したい場合はENTRYPOINTで実行したいコマンドそのもの、CMDでそのコマンドの引数を指定する

#Dockerイメージの取得
FROM ubuntu:16:04

#topを実行
ENTRYPOINT ["top"]
CMD ["-d", "10"]
bash
#デフォルト(Dockerfile)の10秒毎に更新
$ docker container run -it コンテナ名

#2秒毎に更新
$ docker container run -it コンテナ名 -d 2

Docker Compose

bash
#バージョン確認
$ docker-compose --version

#複数コンテナの状態確認
$ docker-compose ps

#ログを確認
$ docker-compose logs

#構成確認
$ docker-compose config

#複数コンテナ生成
$ docker-compose up [オプション] [サービス名]

#./sample/docker-compose.ymlをもとにコンテナを生成
$ docker-compose -f ./sample/docker-compose.yml up

#複数コンテナをバックグラウンド起動
$ docker-compose up -d

#コンテナ起動時にDockerfileをビルド
$ docker-compose up --build

#複数コンテナ起動
$ docker-compose start

#複数コンテナ停止
$ docker-compose stop

#複数コンテナ再起動
$ docker-compose restart

#複数コンテナ一時停止
$ docker-compose pause

#複数コンテナ再開
$ docker-compose unpause

#複数コンテナの強制停止
$ docker-compose kill

#複数コンテナの削除
$ docker-compose rm

#Compose定義ファイルから生成したコンテナやイメージなどのリソースをまとめて削除
$ docker-compose down

#コマンド実行
$ docker-compose run サービス名 コマンド

#サービスの公開ポートを確認
$ docker-compose port [オプション] サービス名 ポート番号

#testサービスの80番ポートに割り当てられた設定を確認
$ docker-compose port test 80
docker-compose.yml
#バージョンを指定
version: ●●●

#ベースイメージを指定
image: イメージ名/イメージID

#Dockerfileによるイメージのビルド
build: Dockerfile

#コマンドを実行
command: コマンド

#entrypointを上書き
entrypoint:
  - ●●
  - ●●

#コンテナ間の連携。エイリアス名を付与することも可能
links:
  - ●●
  - ●●:エイリアス名

#コンテナ間の通信
ports:
  - "ホストのポート番号:コンテナのポート番号"

#コンテナ間の通信。コンテナ内部のみ公開
expose:
  - "ポート番号"

#コンテナの依存関係を定義
depends_on:
  - ●●
  - ●●

#コンテナの環境変数を指定(配列形式)
environment:
  - 環境変数=値

#コンテナの環境変数を指定(ハッシュ形式)
environment:
  環境変数: 

#コンテナの環境変数を指定(別ファイル読み込み)
env_file: ファイル名

#コンテナ名を指定
container_name: コンテナ名

#コンテナのラベル指定(配列形式)
labels:
  - "●●●=■■■"

#コンテナのラベル指定(配列形式)
labels:
  ●●●: "■■■"

Docker Machine

Docker Machine Driver

bash
#バージョン確認
$ docker-machine --version

#実行環境の作成
$ docker-machine create --driver ドライバー名 作成するDockerマシン名

#実行環境の一覧表示
$ docker-machine ls [オプション]

#実行環境のステータス確認
$ docker-machine status Dockerマシン名

#実行環境のURL確認
$ docker-machine url Dockerマシン名

#実行環境へSSH接続
$ docker-machine ssh Dockerマシン名

#実行環境の起動
$ docker-machine start Dockerマシン名

#実行環境の停止
$ docker-machine stop Dockerマシン名

#実行環境の再起動
$ docker-machine restart Dockerマシン名

#実行環境からファイルをダウンロード
$ docker-machine scp Dockerマシン名:ダウンロードするファイル ダウンロード先 

#test実行環境の/etc/passwdファイルをローカルのカレントディレクトリにダウンロード
$ docker-machine scp test:/etc/passwd .

#実行環境の削除
$ docker-machine rm Dockerマシン名

#実行環境の強制停止
$ docker-machine kill Dockerマシン名

#実行環境のIPアドレスを確認
$ docker-machine ip Dockerマシン名

#実行環境の詳細情報を確認
$ docker-machine inspect [オプション] Dockerマシン名

エラー対応

「docker: Error response from daemon: cgroups: cannot find cgroup mount destination: unknown.」

Windows10にDocker Toolsをインストールして利用していた際に発生。

bash
#Docker MachineにSSH接続
$ docker-machine ssh default

#ディレクトリを作成
$ sudo mkdir /sys/fs/cgroup/systemd

#マウント
$ sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

#Docker Machineからログアウト
$ exit

#Docker Machineを再起動
$ docker-machine restart

参考

dockerのイメージ作成が「cgroups: cannot find cgroup mount destination: unknown.」でエラーになる
Docker command でドッカー練習する時のメモ

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

CrowiからGrowi移行時にcreateIndex errorが出たとき

背景

社内運用WikiをCrowiからGrowiへ移行するため、こちらを参考に作業を進めていた。

問題

DBのデータ移行のセクションで、mongorestoreコマンドを実行すると、以下のようなエラー文が出て、データ移行がうまくいかなかった。

Failed: growi.users: error creating indexes for growi.users: createIndex error: Index with name: username_1 already exists with different options

対処法

mongorestoreコマンドのオプションに--noIndexRestoreを付けて実行するとうまくいった。

参考URL

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

WEB開発におけるDockerの使い道を超初心者なりに整理した

この記事を読むにあたっての注意

  • 初心者が備忘録として、Dockerを利用したWEB開発の流れについてまとめたものです。
  • 普段、業務などでDockerを利用し、WEB開発を行っている人などには情報として価値の無い記事です。
  • Dockerのコマンドや、Dockerfile、docker-compose.ymlなどについては一切書いておりません。

本題

Dockerをなぜ使う?

開発環境について
開発環境を開発メンバーごとに手動で用意すると、環境に少なからず差異が生まれてしまう。
これを防ぐために、Dockerを利用し、開発環境のコード実行環境を統一する。
これによって、コードやデータベースのシードデータなどさえ同じならば、コード(実行ファイル)は同じように実行できる。

本番環境について
本番環境(端末など)が複数存在する場合、少なからず環境には差異がある。
どの本番環境(端末)でも同じように動作することを保証するために、Dockerを用いて環境を構築し、実行するコードや実行ファイルを置くなどの最低限の作業(git pullなど)で、動作させる。

開発環境について

Dockerで統一するのは、コードの実行環境(ローカルのテスト環境)。
なので、コードはホストOS側で、好きなエディタなどを用いて書いて、コードが書けたら、必要なものだけをDockerコンテナ内にコピーし(.dockerignoreを利用しても良い)動作確認を行う。

Dockerfileについて

Dockerイメージは開発環境用と本番環境用の2つ用意するのが一般的。故に、Dockerfileを開発環境用と本番環境用の2つを管理するか、マルチステージビルド機能を利用して、単一のDockerfileにまとめる必要がある。
ただし、環境でイメージを使い分けるので、開発環境で正常に動作するからといって、本番環境で正常に動作すると限らない。
したがって、可能な限り、本番環境の動作を保証するための手段を取る必要がある。
対策:
1. 静的コード解析ができるエディタを使用する。
2. 静的コード解析の自動化
3. テストを書く
4. テストの自動化
5. デプロイの自動化(Dockerに対応しているCIツールもある)

他にも、少し話がずれるかもしれないが、権限管理、手入力で行う定型的なコマンドのシェルスクリプト化、作業内容を作業表にまとめておくなどの対策も必要かもしれない

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

docker ymlでnginxの初期設定にマウントとったった。

こんにちは、Docker初心者です。

nginxを使って接続しているローカル環境で、
システムからダウンロードするファイルが重くてTimeoutが起きました。

514エラーです。

nginxかapacheかのエラーなのですが、nginxっぽかったのでやり方を探しました。

マウントという手法

dockerにはマウントという手法があります。(今日初めて知りました。)

マウントって、例の巷で流行りだした新手のハラスメントか…??
安心してください、違います。(知ってるか)

dockerが作ってくれた環境を自由に上書きできるみたいなものだと理解しました。

少し古いですが、こちらの記事を熟読させていただきました。
https://logicoffee.hatenablog.com/entry/2018/06/21/123025

マウント とってみた してみた

ymlファイルでvolumes部分のマウントの とりかた やりかたはこんな感じっぽいです。

ざっくりこんな感じ

docker-compose.yml
./local_dir/fimename.hoge:/docker_container_dir/filename.hoge

local_dir/finelame.hoge

言わずもがな、Dockerファイルを入れいているローカル環境でのファイルへのパス
ディレクトリもファイルも自分で用意するもので、名前とかはどうでもいいやつ。

/docker_container_dir/filename.hoge

composeしたときに勝手にcontainerの中にファイルが生成されていて、
それらのファイルを読み込んで動いているので、
自分が変えたいファイルへのパス

今回の場合

docker-compose.yml
version: '2'
services:
  proxy:
    image: jwilder/nginx-proxy
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ~/etc/timezone:/etc/localtime:ro
      - ./nginx/nginx.conf:/etc/nginx/conf.d/myproxy.conf #追加

そしてtimeoutの時間を延ばすのが目的だったので、
nignx.confの中身はこれだけ。

nignx.conf
fastcgi_read_timeout 6000;
proxy_read_timeout 6000;

これで無事、nginxの初期設定が上書きされ、タイムアウト時間が延びましたとさ!
大変勉強になりました(๑˙❥˙๑)

参考

docker-compose × nignxで大変参考になりました。
https://qiita.com/niibori/items/a68e41a695fd1b1b80a1
https://qiita.com/atsushi586/items/ebb179bc64490f81040c

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

Circle CI でのDocker Buildを超高速化するテクニック

背景

モダンでコンパクトな構成のimageであればCircle CIでのdocker buildはそこまで遅くないものです。しかし諸事情によりわりと大きめのイメージをビルドしないといけない場合があり、5〜10分くらいかかるようになってしまう場合もあります。私の場合古いモノリシックなPHPのサービスをコンテナ化しようとしてそういう事象に至りました。そういった場合に試して効果があったことを解説していきます。

前提

  • dockerでimageを作成するためのベストプラクティス一般はここでは解説しません。もちろん重要なので先にやってください。
  • 若干バッドノウハウ気味な内容も含まれます。採用は自己判断で
  • 自分でJenkins建ててやるとすべてが適切にキャッシュされもっと速いです。しかしそういうことをしたくないのでCircle CIをつかっています

1. Machine Executorを使う

まずとしてMachine Executorを使いましょう。Container Executorの場合はDocker Daemonが別マシンに配置されてしまい。レポジトリまるごとCOPYなどが遅くなる傾向にあります。また後述するローカルのディレクトリをmountして何かを実行するhackが利用できなくなります。

version: 2.1
jobs:
  build-image:
    machine: true
    steps:
      - checkout
      - run:
          name: Build a container
          command: |
            docker login .......
            docker build --progress plain -t $IMAGE_NAME .
            docker push $IMAGE_NAME

2. BuildKitを使う

BuildKitはDockerの新しいシステムで色々最適化されています。デフォルトのMachine Executorのdocker versionは古いため、利用には最新のimageを指定する必要があります。 コマンド実行時はDOCKER_BUILDKIT=1の環境変数を指定することで有効になります。

version: 2.1
jobs:
  build-image:
    machine:
      image: ubuntu-1604:201903-01
    steps:
      - checkout
      - run:
          name: Build a container
          command: |
            docker login .......
            DOCKER_BUILDKIT=1 docker build --progress plain -t $IMAGE_NAME .
            docker push $IMAGE_NAME

3. Docker Layer Cachingを使う

Docker Layer Cachingはdockerのキャッシュ周りのディレクトリを永続化し、Executor実行時にマウントしてくれる仕組みです。おもにDockerfileのFROMで指定するベースイメージが大きい場合などにダウンロードが省略できるため大幅な時間短縮が見込めます。ただしExecutor起動時にマウント処理が5〜10秒ほどかかるようになるためベースイメージが小さい場合には利用しないほうが高速となるでしょう。

version: 2.1
jobs:
  build-image:
    machine:
      image: ubuntu-1604:201903-01
      docker_layer_caching: true
    steps:
      - checkout
      - run:
          name: Build a container
          command: |
            docker login .......
            DOCKER_BUILDKIT=1 docker build --progress plain -t $IMAGE_NAME .
            docker push $IMAGE_NAME

4. Vendoringなどの作業をdocker buildするExecutor内で行う

Vendoringとは npm install, bundle install, composer install などの作業です。いくつかやり方がありますが
CircleCI上で実施する場合つぎのような方法が考えられます。が、それぞれ問題があります

  • Workflow内の上流jobとして実施しWorkspaceやCache経由で受け渡す
    • JobごとにExecutorの起動などが発生し最大で20〜30秒ほどの時間がかかる
  • Dockerfile内で行う
    • Executorが永続化していないためDocker本来のキャッシュ機構が利用しにくい

そのためlocalのディレクトリをdockerにマウントしてvendoringを実行。結果はCircle CIのcacheに保存するという戦略が有効になります。

version: 2.1
jobs:
  build-image:
    machine:
      image: ubuntu-1604:201903-01
      docker_layer_caching: true
    steps:
      - checkout
      - restore_cache:
          keys:
            - vendoring-{{ checksum "composer.lock" }}
      - run:
          name: Vendoring
          command: |
            docker login .......
            docker run --rm -v /home/circleci/repo:/home/circleci/repo \
                ${BASE_IMAGE} \
                bash -c \
                "cd /home/circleci/repo && \
                php composer.phar config --global github-oauth.github.com ${GITHUB_TOKEN} && \
                php composer.phar install --prefer-dist --ignore-platform-reqs --no-scripts --no-dev"
      - save_cache:
          key: vendoring-{{ checksum "composer.lock" }}
          paths:
            - vendor
      - run:
          name: Build a container
          command: |
            DOCKER_BUILDKIT=1 docker build --progress plain -t $IMAGE_NAME .
            docker push $IMAGE_NAME

コードでわかると思うのですが速度と引き換えに可読性が落ちるという問題があります。また複数処理を並列に実行なども可能ですが複雑になりすぎるためおすすめできません。

5. shallow-checkoutを利用する

これはdockerとは関係ないのですが、さらなる高速化の一手としてgit checkoutを高速化するという方法があります。Circle CI標準のcheckoutではgitレポジトリをまるごとcloneしています。現在のcommitでの状態のみをチェックアウト (shallow checkout)することでこのcheckoutにかかる時間を短縮できます。なぜかMachine Executorではgit checkoutがContainer Executorよりも遅い傾向があり、さらに有効です。

今回はcommandに直接内容を記述していますがOrbが利用できる環境ではOrbにしたほうが保守性が高いかもしれません。

version: 2.1
jobs:
  build-image:
    machine:
      image: ubuntu-1604:201903-01
      docker_layer_caching: true
    steps:
      - shallow-checkout
      - restore_cache:
          keys:
            - vendoring-{{ checksum "composer.lock" }}
      - run:
          name: Vendoring
          command: |
            docker login .......
            docker run --rm -v /home/circleci/repo:/home/circleci/repo \
                ${BASE_IMAGE} \
                bash -c \
                "cd /home/circleci/repo && \
                php composer.phar config --global github-oauth.github.com ${GITHUB_TOKEN} && \
                php composer.phar install --prefer-dist --ignore-platform-reqs --no-scripts --no-dev"
      - save_cache:
          key: vendoring-{{ checksum "composer.lock" }}
          paths:
            - vendor
      - run:
          name: Build a container
          command: |
            DOCKER_BUILDKIT=1 docker build --progress plain -t $IMAGE_NAME .
            docker push $IMAGE_NAME
commands:
  shallow-checkout:
    description: "from: https://circleci.com/orbs/registry/orb/datacamp/shallow-checkout"
    steps:
      - run:
          name: Shallow checkout
          command: |
            set -e

            # Workaround old docker images with incorrect $HOME
            # check https://github.com/docker/docker/issues/2968 for details
            if [ "${HOME}" = "/" ]
            then
              export HOME=$(getent passwd $(id -un) | cut -d: -f6)
            fi

            mkdir -p ~/.ssh
            # 全員共通の値かわからなかったのでmaskしています。各自自分の環境でのcheckoutの実行ログからコピーしてfillしてください
            echo 'github.com ssh-rsa XXXXXXXXXXXXXXXX 
            ' >> ~/.ssh/known_hosts

            (umask 077; touch ~/.ssh/id_rsa)
            chmod 0600 ~/.ssh/id_rsa
            (echo $CHECKOUT_KEY > ~/.ssh/id_rsa)

            # use git+ssh instead of https
            git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true
            git config --global gc.auto 0 || true

            mkdir -p $CIRCLE_WORKING_DIRECTORY
            cd $CIRCLE_WORKING_DIRECTORY

            if [ -n "$CIRCLE_TAG" ]
            then
              git clone --depth=1 -b "$CIRCLE_TAG" "$CIRCLE_REPOSITORY_URL" .
            else
              git clone --depth=1 -b "$CIRCLE_BRANCH" "$CIRCLE_REPOSITORY_URL" .
            fi
            git fetch --depth=1 --force origin "$CIRCLE_SHA1" || echo "Git version >2.5 not installed"

            if [ -n "$CIRCLE_TAG" ]
            then
              git reset --hard "$CIRCLE_SHA1"
              git checkout -q "$CIRCLE_TAG"
            elif [ -n "$CIRCLE_BRANCH" ]
            then
              git reset --hard "$CIRCLE_SHA1"
              git checkout -q -B "$CIRCLE_BRANCH"
            fi

            git reset --hard "$CIRCLE_SHA1"

まとめ

自分の利用している環境ではPull Requestにcommitがpushされると自動でKubernetesのdev環境にdeployされる仕組みを整えており、buildにかかる時間はとても重要なものでした。500MBほどの大きなイメージを扱っており最適化前は5分以上かかっていたbuildがこれらの高速化を利用することで90秒ほどで終わるようになっています。
最適化はやりすぎると可読性や保守性が失われてしまうので良いバランスでやっていきたいですね。

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

mosquittoのDockerImageを動かしてみる

Mosquittoをwindows10上でDockerで動かしてみる

MosquittoはOpenSourceのMQTTブローカーです。この記事ではwindows10上でMosqiuttoのDockerイメージを動かすまでの手順について説明します。

MosquittoをDockerで動かす理由

Mosquitto自体はWindows上に簡単にインストールして動かすことが出来ます。しかしながらWindows版でWebSocketをサポートするには自分でソースからビルドしないといけないようです。(Macならそのまま使えます)
そこで、Docker Hubで検索してみたところ以下のイメージがあったので、使ってみることにしました。

https://hub.docker.com/_/eclipse-mosquitto

eclipse-mosquittoの実行

実行に関しては非常に簡単です。以下のコマンドでイメージを取得して実行します。
デフォルトの構成ファイルでは1883ポートでMQTTのポートがオープンします。
docker run -it -p 1883:1883 eclipse-mosquitto

構成ファイルの指定

デフォルトの構成ではMQTTのみのサポートのため、構成ファイルを指定してwebsocketのリスナーも起動します。今回はローカルにインストールしたMosquittoの構成ファイルの以下の記述を追加しました。

mqtt.conf
listener 1883
listener 9001
protocol websockets

以上の設定で、MQTTは1883、websocketsは9001のポートを使用するようになります。
起動時に-vオプションで構成ファイルを明示的に指定します。
なお、ファイルの場所は絶対パスで指定しないと駄目なようです。

$ docker run -it -p 1883:1883 -p 9001:9001 -v C:\mosquitto\mosquitto.conf:/mosquitto/config/mosquitto.conf eclipse-mosquitto

実行結果

C:\mosquitto>docker run -it -p 1883:1883 -p 9001:9001 -v C:\mosquitto\mosquitto.conf:/mosquitto/config/mosquitto.conf eclipse-mosquitto
1580886921: mosquitto version 1.6.8 starting
1580886921: Config loaded from /mosquitto/config/mosquitto.conf.
1580886921: Opening websockets listen socket on port 9001.
1580886921: Opening ipv4 listen socket on port 1883.
1580886921: Opening ipv6 listen socket on port 1883.

まとめ

Dockerを用いることで簡単にローカルでMosquittoを起動することができました。
次のステップとしてDockerイメージをビルドしてIBM Cloud上で動作させたいと思っています。

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

AWS EC2 AmazonLinux2 のdockerホスト用初期設定

やりたいこと

AmazonLinux2に、以下の設定をまとめて実行する。

  • タイムゾーンを日本に
  • 言語を日本語に
  • git docker docker-compose 最新版インストール
  • 以下のコマンドのエイリアス(ショートカット)作成
元のコマンド エイリアス
docker dcr
docker-compose ddc

追記: dockerのエイリアスを dcr に修正しました:wink:

コマンド

sudo yum update -y
sudo cp /etc/localtime /etc/localtime.org
sudo ln -sf /usr/share/zoneinfo/Japan /etc/localtime
sudo mv /etc/sysconfig/clock /etc/sysconfig/clock.bk
sudo echo -e 'ZONE='Asia/Tokyo'\nUTC=true' > /etc/sysconfig/clock
sudo mv /etc/sysconfig/i18n /etc/sysconfig/i18n.bk
sudo echo 'LANG=ja_JP.UTF-8' > /etc/sysconfig/i18n
export LANG=ja_JP.utf8
export LC_ALL=ja_JP.utf8


sudo yum install -y git jq

sudo amazon-linux-extras install -y docker
sudo service docker start
sudo chkconfig docker on
sudo usermod -a -G docker ec2-user #本番環境などでは危険だからやらないでね!

compose_version=$(curl https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)
output='/usr/local/bin/docker-compose'
sudo curl -L https://github.com/docker/compose/releases/download/$compose_version/docker-compose-$(uname -s)-$(uname -m) -o $output
sudo chmod +x $output

echo "alias dcr='docker'" >> ~/.bashrc
echo "alias ddc='docker-compose'" >> ~/.bashrc

source ~/.bashrc

おまけ: rootでもdocker-composeを使う場合の設定

sudo visudo

sudoers 編集画面を開き

# :/usr/local/binをおしりに追加
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

の通りに編集して、保存。

そして以下のコマンドを実行。

sudo sh -c "echo alias dcr='docker' >> /root/.bashrc"
sudo sh -c "echo alias ddc='docker-compose' >> /root/.bashrc"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCE 環境でDockerCompose環境のMySQLのdocker-entrypoint-initdb.dが動かない時

マウントしたフォルダがPermission Denied、そんなはずはない!
777付与もしたけど動かない!そんなあなたに。

原因はSELinux。

chcon -Rt svirt_sandbox_file_t [マウント対象フォルダのパス]

これでディレクトリ単位で許可することができる。
再度 dokcer-compose upすれば

動きます

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

GCE 環境でMySQLのdocker-entrypoint-initdb.dが動かない時

マウントしたフォルダがPermission Denied、そんなはずはない!
777付与もしたけど動かない!そんなあなたに。

原因はSELinux。

chcon -Rt svirt_sandbox_file_t [マウント対象フォルダのパス]

これでディレクトリ単位で許可することができる。
再度 dokcer-compose upすれば

動きます

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