20200802のdockerに関する記事は17件です。

[Docker]覚えておきたいコマンドのオプションまとめ

はじめに

Dockerのコマンドは普段から動かしているけど、

  • コマンドのオプションを丸暗記で打つのではなく、きちんと理解する
  • オプションの数が多いので、自分で利用する範囲で覚える

という趣旨で、自分がDockerを使っているうちに必要になったオプションを纏めました。
(※自分用のメモでもあるので、今後内容を増やしていくかもしれません。)

docker ps

  • -a, --all: このオプションを使うことで、停止しているコンテナも含めて表示することができる。デフォルトでは実行中のコンテナしか表示されないため、「docker psでコンテナ有無確認→無いのでdocker runで以前起動したことのある名前でコンテナを実行→同じ名前のコンテナがあるので落ちる」というのを何回もやってしまった。
  • -q, --quiet: DockerコンテナのIDのみを一覧表示する。一括でコンテナを削除したい場合等に使える。
  • -f, --filter: フィルタを入れる。例えば、停止中の一覧を表示させたいのなら docker ps -f "status=exited" となる。公式ドキュメントにて、どういうフィルタが利用できるのか確認できる。

docker run

  • -d, --detach: コンテナをバックグラウンドで実行する。
[root@a-kfh1mrzyo7pr ishizawa_r]# docker run -it ubuntu # -dを付けてないので、ubuntuのターミナルに入る
root@6cb3394b10c4:/# exit
exit
[root@a-kfh1mrzyo7pr ishizawa_r]# docker ps # コンテナはターミナルから出ると停止されるので、docker ps では表示されない
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

[root@a-kfh1mrzyo7pr xxx]# docker run -d -it ubuntu # -dを付けて実行
51c588e4e33b812c80e76639da8f61fff0c46662d33e8df72451c8692ff81126
[root@a-kfh1mrzyo7pr ishizawa_r]# docker ps # コンテナがバックグラウンドで実行されているので、docker ps で表示される
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
51c588e4e33b        ubuntu              "/bin/bash"         2 seconds ago       Up 1 second                             reverent_buck
  • -e, -env: 環境変数の設定。
  • -i , --interactive: STDIN(標準入力)をオープンにし続ける。コマンドの結果を受け取るために必要。(参考)
  • -name: 名前。名前をつけない場合、ランダムでコンテナの名前が付けられる。docker psで名前を確認できる。
  • --network: ネットワーク設定。例えば、--network="host" とすると、Docker Hostのネットワークが利用できる。ネットワークの設定をしておかないと、docker execでパッケージのアップデート(apt-get)等を行うことすらできない場合があるので、docker runを実行する際に設定しておくと良い。Hostネットワークについてはこちらの記事が分かりやすい。
  • -t: 公式ドキュメントには "Allocate a pseudo-TTY"(疑似TTYを割り当てる) とある。ttyというのは標準入出力となっている端末デバイスのことで、ターミナルでの操作を行う際に違いが出る。ターミナル内で何かオペレーションをする可能性がある場合には付けておくと良いオプション(という理解)。こちらにもう少し詳細の挙動の違いが記載されている。
    • -itというのが出てきたら、-i-tを両方付けたオプションということ。
  • -v, --volum: フォルダをマウントする。Dockerコンテナとローカルで双方向に簡単にファイル共有ができる。(参考)

docker rm

  • -f: 実行中のコンテナであっても削除する。デフォルトでは、実行中のコンテナは停止してから削除する必要があり、このオプションでショートカットできる。

docker exec

  • -i-t はdocker runと同じ。bashのコネクションを張る時は基本的に -it を付けること、と覚えておく。

参考

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

Dockerとイメージの作成からビルドまで

はじめに

この記事は、インターネット上の百科事典や技術情報サイト、ライブ配信を基に、Dockerについて初学者が学習した内容をまとめた備忘録です。
技術的に誤っている点がございましたら、ご指摘いただけますと幸いです。

Dockerとは?

Docker社が開発しているコンテナ型の仮想環境(MacやWindowsなどのホストOS)を作成、配布、実行するためのプラットフォームです。
アプリケーションのデプロイを簡単に行ってくれます。

Dcckerの使い方

Dockerコンテナの操作手順は、Dockerインストール後、Dockerhubでイメージを作成し、コンテナへのビルド、という流れで行われます。

docker_image_container.jpg

出典: <Docker入門(第二回)~Dockerセットアップ、コンテナ起動~>

インストール

Docker公式サイトの「Get Started」からインストールできます。
https://www.docker.com

複数のコンテナを一元管理

Docker Composeを公式サイトからインストールし、利用すると、Apache、MySQL、PHPなど複数のコンテナを同時に立ち上げることができます(この記事では紹介のみ行わせていただきます)。
https://docs.docker.jp/compose/toc.html

イメージ作成

Docker Hub公式サイトからDockerイメージを取得することで、Dockerコンテナを起動後、すぐに使用できます。
https://hub.docker.com

Dockerコンテナの実行例

Dockerがインストールされた環境で、NginxのDockerイメージを使ってWebサーバーを立ち上げるために、以下を実行します。

docker run --name some-nginx -d -p 8080:80 nginx

Dockerfileからイメージ作成

イメージはDockerfileにベースイメージを指定し、コードに環境構築の手順を記載して作成することができます。
実際に、Nginxのイメージを作成します。

  • Nginxの設定ファイルを作成します。
mkdir nginx
cd nginx
touch default.conf
  • default.confを編集します。
server {
    listen       8080;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
  • Dockerfileを作成します。
touch Dockerfile
  • Dockerfileを編集します。
FROM nginx:alpine
COPY ./default.conf /etc/nginx/conf.d/
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
  • 作成したDockerfileをビルドします。
docker build . -t alpine_nginx

備考

アウトプットもかねて、個人的に要点をおさえた内容を記述させていただきました。
最近は、AWSやAzureどのクラウドサービスでDockerが簡単に使えるサービスがあるようですが、クラウド上にデプロイできると便利そうです。
ここまで拝読していただき、ありがとうございました。

参考文献

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

DockerでビルドしたQWidgetアプリをMacに表示

MacにXサーバをインストール

XQuartz からMacにXサーバをインストールする。

Xサーバを起動してXサーバへのアクセスを許可

インストールしたXQuartzを実行してxtermを起動し、xhostにてXサーバへのアクセスを許可するように変更する。

xhost +

DockerでQtのサンプルアプリをビルド

Qtビルド環境のDockerの作成 で作成したイメージからコンテナを作成する。

docker run -it --name test masana/ubuntu-qtenv:qt5.10.1 /bin/bash

実行に必要なモジュールをインストール

作成したコンテナにおいて、xcbクライアントの実行に必要なモジュールをインストールする。

apt-get install -y libxi6 libxrender1 

ビルドだけでなく実行を前提とするなら、ビルド環境を作成するDockerfileにて上記を追記しておく。

サンプルアプリを実行

作成したコンテナにおいて、ビルドしたサンプルアプリケーションを実行する。

/tmp/sample/sample -display {ホストのMacのIPアドレス}:0.0

MacにQtのウィンドウが表示される。

  • Qtのサンプルプログラム
#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QLabel label;
    label.setText("TEST");
    label.setAlignment(Qt::AlignCenter);
    label.setGeometry(100,100,500,500);
    label.show();

    return a.exec();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerのイメージとコンテナの消去について

目的

dockerを動かそうといろいろな検証をしているうちにいらないイメージができたので、その削除方法について調べる。

コンテナの停止と削除

現在動いているコンテナはdocker psで確認することができる。
exitコマンドなどで抜け出したコンテナは停止中のため、ここには表示されない。
docker ps -aのコマンドで停止中のコンテナも確認することができる。

$ docker ps -a
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                       PORTS                                      NAMES
63c984412464        doctest                     "python3"                7 hours ago         Exited (137) 7 hours ago                                                doctest

今回はこのコンテナを削除してみる。
コマンドはdocker rm [コンテナID|Name]
また、ここでIDを入力する場合、全てを打ち込まなくても、一意に判断できる文字列を入力することで削除できる。
今回はdocker rm 63cと打つだけで削除が可能だ。

イメージの削除

イメージを削除しても、そのイメージを含むコンテナは削除されない。(ここ大切)
なので、削除したいイメージがある場合は先にコンテナを削除しておくのが吉。

docker imagesでイメージの一覧を取得できる。

$ docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
python                               3.5.8               0688d5a54cf4        9 months ago        908MB

今回はこのpythonの3.5.8を削除する。

コマンドはdocker rmi [REPOSITORY NAME[:TAG]|Image ID]でコンテナ同様、全てを打ち込む必要はない。

$ docker rmi 068
Error response from daemon: conflict: unable to delete 0688d5a54cf4 (cannot be forced) - image has dependent child images

子のimageがあると警告が出される。-f (force)のオプションをつけると強制的に消せる。

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

【Rails】 APIキーやDBのパスワードをcredential.ymlに記述する

※自分用メモです。

1.credentials.yml.encに隠したい情報を記述する

まずcredentials.yml.encを編集します。

コマンド
docker-compose run -e EDITOR=vim web rails credentials:edit

Dockerを使用している場合
vimをインストールしていないとエラーが出るのでインストールしておきましょう。

Dockerfile
RUN apt-get install -y vim
credentials.yml.enc
db:
  password: XXXXXXX

api_key:
  google: XXXXXX

2.呼び出し方法

database.yml

database.yml
  password: <%= Rails.application.credentials.db[:password]%>

~.erb

~~html.erb
<script src="https://maps.googleapis.com/maps/api/js?key=<%= Rails.application.credentials.api_key[:google]%&callback=initMap" async defer></script>

補足

.gitignoreでmaster.keyが記述されているか確認しておきましょう。

gitignoreにはgithubにpushしないファイルを設定するファイルになります。

master.keyはデフォルトで.gitignoreに記述されていますが、念のため確認します。

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

Dockerを使ったRails開発でブラウザテストが実行できない

概要

・RSpecでCapybaraを使ったブラウザテストを実装する際にエラー

RSpecの学習をしている際にテストを実行すると下記のエラーが発生しました。
解決するのに結構時間がかかりました。

Selenium::WebDriver::Error::UnknownError:
            unknown error: Chrome failed to start: exited abnormally.
              (unknown error: DevToolsActivePort file doesn't exist)
              (The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)
spec-helper.rb
  Capybara.register_driver :selenium_chrome_headless do |app|
    browser_options = ::Selenium::WebDriver::Chrome::Options.new()
    browser_options.args << '--headless'
    browser_options.args << '--no-sandbox'
    browser_options.args << '--disable-gpu'
    Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
  end

解決方法

Dockerfileにchrome driverをインストールする記述を追加したところ上手くテストが実行されました。

追記分

Dockerfile
RUN CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
    wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \
    unzip ~/chromedriver_linux64.zip -d ~/ && \
    rm ~/chromedriver_linux64.zip && \
    chown root:root ~/chromedriver && \
    chmod 755 ~/chromedriver && \
    mv ~/chromedriver /usr/local/bin/chromedriver && \
    sh -c '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-chrome.list' && \
    apt-get update && apt-get install -y google-chrome-stable


動作確認済みDockerfile↓

Dockerfile
FROM ruby:2.5
RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    node.js \
    yarn 
# Rspecで使うchormedriverをインストール
RUN CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
    wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \
    unzip ~/chromedriver_linux64.zip -d ~/ && \
    rm ~/chromedriver_linux64.zip && \
    chown root:root ~/chromedriver && \
    chmod 755 ~/chromedriver && \
    mv ~/chromedriver /usr/local/bin/chromedriver && \
    sh -c '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-chrome.list' && \
    apt-get update && apt-get install -y google-chrome-stable

# 作業ディレクトリに移動(無ければ自動で作成)
WORKDIR /app
#build context内のGemfileとGemfile.lockをコピー
COPY Gemfile Gemfile.lock /app/
#Gemをインストール
RUN bundle install

Dockerfileを編集しているので

$docker-compose build

再度コンテナを起動してテストを実行するとうまくテストが実行されました。

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

WSL2 + Ubuntu 20.04 + Docker 開発環境構築

序論

本稿は 元市役所職員がWEBプログラマに転職するまでのロードマップ の連載記事の一部です。

まだ、WEBプログラマに転職してから2年も経過していない素人であるため、色々と間違っていることを書いていたりするかと思います。
その際はお手数ではございますが、ご指摘いただければ幸いでございます。

仮想化技術

WSL2やDockerの開発環境を構築する前に、仮想化技術について簡單に触れておきます。

仮想化技術には大きく以下の3種類があり、いずれも「隔離されたアプリケーション実行環境」を提供するものです。

  • ホスト型
    • vtype-host.png
    • ホストOSの上に別のOS(ゲストOS)をインストールする仮想化技術
    • ゲストOSをそのまま仮想環境にインストールするため、ある程度の再現性が担保されている
    • ディスク容量やメモリ使用量が大きい、動作が重い・不安定などの欠点がある
  • ハイパーバイザ型
    • vtype-hyper.png
    • ハイパーバイザとはホストOSそのものを仮想化する制御プログラム
    • ホスト型より性能劣化が低い
    • ゲストOSとして使用できるOSに制限がある
  • コンテナ型
    • vtype-container.png
    • ホストOSのカーネルを流用して隔離されたアプリケーション実行環境(コンテナ)を実現する仮想化技術
    • 性能劣化がほぼなく、安定して動作する
    • カーネルの異なるゲストOSを利用することはできない(例えば、Linuxホスト上でコンテナとして利用できるのはLinux系OSのみであり、Windows OS等を使うことはできない)

この仮想化技術の発展により、Web開発は大きく進展したと言われています。

従来は、物理的なサーバマシンに様々なアプリケーションをまとめて放り込んでいるような状態であったため、以下のような問題がありました。

  • 開発環境とサーバ環境を同一の状態にすることが難しく、開発時に動作していたものがサーバ公開時に動作しなくなるなどの問題が発生しやすい
  • 一つのアプリケーションに問題が発生した場合、他の正常稼働しているアプリケーションにも影響が出る
  • サーバ用途の転用が困難(ハードウェア構成の変更作業が発生する)
  • アプリケーション・ミドルウェアの構成を自由に変更することが困難
  • アプリケーション・ミドルウェアの構成を自動化することが困難

上記のような問題のほとんどが、仮想化技術により解決されたと言われております。

特にコンテナ型の仮想化技術は、ホストOSの上で直接動作するため、ほぼ性能劣化することなく隔離されたアプリケーション実行環境を提供することができるとされております。
そのため、現在のWeb開発においてはこのコンテナ型仮想環境を利用するのが主流になりつつあり、特に、Docker社の開発した Docker は、コンテナ型仮想化技術として広く普及しています。

実際、Google社のWebサービスなどはあらゆるものがコンテナ化されて運用されているという話もあります。

WindowsにおけるWEB開発

前述の通り、WEB開発においてはコンテナ型仮想環境を用いてアプリケーション実行環境ごと隔離して開発するのがスタンダードとなってきています。
しかしながら、WEBサーバとして利用されるマシンはLinux系OSがほとんどであり、Dockerも基本的にはLinux用に開発されています。
そのため、Windows等の別のOS上で動かすには、VirtualBox や VMware 等のホスト型仮想環境の上にLinux系OSをインストールして使うか、WSL2(ハイパーバイザ型仮想環境)上にLinux系OSをインストールして使うことになります。

WSL2 が正式リリースされた 2020年5月 までは、VirtualBox + Vagrant (環境構築自動化ツール) というホスト型仮想環境を使うことが多かったように見受けられますが、以下のような問題があり、WindowsでWEB開発を行う場合は WSL2 を使うことが多くなってきている気がします。(筆者の個人的な感覚なので、情報ソースは曖昧です)

  • VirtualBox, Vagrant, Vagrant Plugin のバージョンごとに相性があり、バージョンが変わるだけで上手く動作しないことが多い
  • ホスト型仮想環境であるため、メモリ使用量が比較的多い
  • WindowsファイルシステムとLinuxファイルシステムの相互変換コストが大きく、動作が遅かったり、ハードリンク系のファイル操作が上手く働かなかったりする
  • GPUリソースを扱うことができない

WSL2について

正式名称 Windows Subsystem for Linux 2 で、ハイパーバイザ型の仮想環境です。

VirtualBox + Vagrant や VMware を使うよりシームレスに Linux 環境を利用することができ、個人的には仮想環境由来のおかしなトラブルが大きく減ったと感じております。

ただし、Windowsにおけるハイパーバイザ型仮想環境の宿命として、ホスト型仮想環境との共存はできないため、Virtual + Vagrant や VMware の環境は封印する必要があります

本稿では、WSL2 を導入し、その上に Ubuntu 20.04 (Linux系OSの中でも最近人気の高いディストリビューション) をインストール => Docker 環境を構築します。

Environment

  • Host
    • OS: Windows 10
      • バージョン 2004, ビルド 19041 以上
  • Guest
    • OS: Ubuntu 20.04
    • Linuxbrew: 2.4.2
    • anyenv: 1.1.1
      • pyenv: 1.2.19
        • Python2: 2.7.18
        • Python3: 3.7.7
        • pip package manager: 20.1.1
        • AWS CLI: 1.18.93
      • nodenv: 1.3.2
        • Node.js: 10.17.0
        • Yarn package manager: 1.22.4
        • Gulp task runner: 2.3.0
    • PHP: 7.4.3
      • composer package manager: 1.10.8
    • Docker: 19.03.12
      • docker-compose: 1.26.0
    • Ansible: 2.9.10

Setup

まず、WSL1 を導入し、その上に Ubuntu 20.04 をインストールします。

Win + X |> A キーで管理者権限 PowerShell を起動し、以下のコマンドを実行します。

# Windows Subsystem for Linux を有効化する
> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
この操作を完了するために、今すぐコンピューターを再起動しますか?
[Y] Yes  [N] No  [?] ヘルプ (既定値は "Y"): # そのままENTERして再起動

# 再起動したら Ubuntu 20.04 ディストロパッケージをダウンロード
## 「ダウンロード」ディレクトリに ubuntu2004.appx というファイル名でダウンロード
> Invoke-WebRequest -Uri https://aka.ms/wslubuntu2004 -OutFile ~\Downloads\ubuntu2004.appx -UseBasicParsing

# ダウンロードしたディストロパッケージをWSLにインストール
> Add-AppxPackage ~\Downloads\ubuntu2004.appx

インストールが完了したら、Windows スタートメニューから Ubuntu 20.04 LTS を起動します。

# -- Ubuntu 20.04 Terminal

# 初回起動時は初期設定が必要
Installing, this may take a few minutes...
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: # <= ログインパスワードを設定(sudo コマンド実行時等に必要なため忘れないようにする)
Retype new UNIX password: # <= ログインパスワードをもう一度入力

# 初期設定を行うと WSL に Ubuntu 20.04 ディストロが追加される
# ここで一旦終了する
$ exit

WSL2 へのアップグレード

WSL1 では、完全にすべてのLinuxプログラムが動作するわけではありません。(例えば、複数のDockerコンテナを管理する docker-compose などは動作しない)

一方で、WSL2 は完全なLinuxカーネルを使用しており、docker-compose 等も問題なく動作します。

WSL2 は Windows 10 バージョン 2004 で一般提供されておりますが、もしお使いの Windows 10 のバージョンがそれより低い場合は、Windows Update を実行する必要があります。(ここでは説明割愛)

Win + X |> A キーで管理者権限 PowerShell を起動し、以下のコマンドでWSL2へのアップグレードを行います。

# WSL2 を使うために、Windows仮想化機能(Hyper-V)を有効化
> Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
この操作を完了するために、今すぐコンピューターを再起動しますか?
[Y] Yes  [N] No  [?] ヘルプ (既定値は "Y"): # そのままENTERして再起動

# 再起動が完了したらWSLのバージョン確認
## 現状の Ubuntu 20.04 は Version 1 になっているはず
> wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-20.04    Stopped         1

# 先にインストールしていた Ubuntu 20.04 を WSL2 環境に変換する
> wsl --set-version Ubuntu-20.04 2

# 「WSL 2 を実行するには、カーネル コンポーネントの更新が必要です。」というエラーが出た場合
## => https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi をインストールして再実行する

# 変換が完了したらバージョン確認
## Ubuntu 20.04 が Version 2 になっていればOK
> wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-20.04    Stopped         2

開発ディレクトリについて(重要)

WSL2 環境において開発ディレクトリをどこに置くかは重要です。

開発ディレクトリを Windowsファイルシステム側(Linuxパス: /mnt/c/...)に置いた場合、ファイル IO が異常に遅く、一部 Docker 環境ではネットワーク通信に不具合が発生するなどの問題が起こります。
そのため、基本的には \\wsl$\Ubuntu-20.04\home\<ユーザ名>(Linuxパス: /home/<ユーザ名>)など、Linuxファイルシステム側に開発ディレクトリを置く必要があります。
開発ディレクトリがLinuxファイルシステム側に置いてあれば、Dockerプロジェクトも安定・軽快に動かすことができます。(少なくとも今のところは)

wsl2_path_windows.png

wsl2_path_linux.png

Ubuntu 20.04 Setup

Ubuntu 20.04 on WSL2 は、以下のいずれかの方法で起動できます。

  1. Windowsスタートメニューの Ubuntu 20.04 LTS から起動
  2. PowerShell で start wsl コマンドから起動
  3. Windowsエクスプローラのアドレスバーに wsl と打てばエクスプローラで開いているディレクトリ内で起動することも可能

wsl_from_explorer.png

開発ツール導入

# -- Ubuntu 20.04 on WSL2

# Linuxシステムアップデート
$ sudo apt update && sudo apt upgrade -y

# Linuxbew の動作に必要な curl, git, ruby をインストール
## openjdk は android 開発を行う時など必要になるタイミングが多いため一応インストールしている
## zlib1g-dev, libssl-dev, libbz2-dev, libsqlite3-dev, libffi-dev は Python ビルドに必要
## add-apt-repository コマンドを使うために software-properties-common もインストールしておく
## https通信を可能にするために apt-transport-https, ca-certificates もインストールしておく
$ sudo apt install -y vim curl git ruby openjdk-14-jdk \
    zlib1g-dev libssl-dev libbz2-dev libsqlite3-dev libffi-dev \
    software-properties-common apt-transport-https ca-certificates

# Linuxbrew (Linux版の Homebrew パッケージマネージャ) 導入
## Linuxbrew を使うことで最新の開発ツール等を導入しやすくなる
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
## PATHを通す
$ echo 'export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc

## Linuxbrew をアンインストールする場合
# $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
## 残ってしまった場合は直接ディレクトリ削除
# $ sudo rm -rf /home/linuxbrew/

# Linuxbrew で各種開発ツールを導入
## curl や git などは、最新版を使う方が良いため、改めて Linuxbrew で導入しなおす
$ brew install curl git wget gcc zlib libzip bzip2 readline openssl pkg-config autoconf

anyenv 導入

  • anyenv
    • env系開発環境をまとめて管理できるツール
    • env系開発環境とは、pyenv, nodenv など、各プログラミング言語の複数バージョンを切り替えて使用可能とする環境のこと
    • 独自に導入した env系開発環境がある場合は、それらを削除してから導入すること
# -- Ubuntu 20.04 on WSL2

# Linuxbrew で anyenv 導入
$ brew install anyenv
$ anyenv install --init
## Do you want to checkout ? [y/N]: <= y

# anyenv 初期化スクリプトを .bashrc に記述
$ echo 'eval "$(anyenv init -)"' >> ~/.bashrc
$ source ~/.bashrc

# anyenv update plugin の導入
$ mkdir -p $(anyenv root)/plugins
$ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
$ anyenv update

# バージョン確認
$ anyenv -v
anyenv 1.1.1

Python 環境構築

Python は、AWS CLI や Ansible の他にも、Node.js の native-addon-build-tool などにも使われています。
Python 自体を開発言語として使わなくても、様々なツールの動作に必要になることが多いため、導入しておくことを推奨しています。

# -- Ubuntu 20.04 on WSL2

# anyenv を使って pyenv 導入
## pyenv を使うことで、複数バージョンの Python 環境を構築できる
$ anyenv install pyenv
$ exec $SHELL -l

# pyenv で Python 2.7.18 と 3.7.7 をインストール
$ pyenv install 2.7.18
$ pyenv install 3.7.7

# pyenv では 2系 と 3系 を同時に指定できる
## python  => 2.7.18
## python3 => 3.7.7
$ pyenv global 2.7.18 3.7.7

# 現在選択されているバージョンを確認
$ pyenv versions
* 2.7.18 (set by /home/user/.anyenv/envs/pyenv/version)
* 3.7.7 (set by /home/user/.anyenv/envs/pyenv/version)

$ python --version
2.7.18

$ python --version
3.7.7

# pip パッケージマネージャを更新しておく
$ pip install --upgrade pip setuptools
$ pip3 install --upgrade pip setuptools

$ pip --version
pip 20.1.1 from /home/user/.anyenv/envs/pyenv/versions/2.7.18/lib/python2.7/site-packages/pip (python 2.7)

$ pip3 --version
pip 20.1.1 from /home/user/.anyenv/envs/pyenv/versions/3.7.7/lib/python3.7/site-packages/pip (python 3.7)

Node.js 環境構築

フロントエンド開発で Node.js は導入必須のため、nodenv を使って Node.js 環境を構築しておきます。

# -- Ubuntu 20.04 on WSL2

# anyenv を使って nodenv 導入
## nodenv を使うことで、複数バージョンの Node.js 環境を構築できる
$ anyenv install nodenv
$ exec $SHELL -l

## nodenv-yarn-install プラグイン導入: nodenv install 時に yarn もインストールする
$ mkdir -p "$(nodenv root)/plugins"
$ git clone https://github.com/pine/nodenv-yarn-install.git "$(nodenv root)/plugins/nodenv-yarn-install"
$ echo 'export PATH="$HOME/.yarn/bin:$PATH"' >> ~/.bashrc

# Node.js 10.17.0 インストール
$ touch $(nodenv root)/default-packages
$ nodenv install 10.17.0

# Node.js 10.17.0 に切り替え
$ nodenv global 10.17.0

# 現在選択されているバージョンを確認
$ nodenv versions
* 10.17.0 (set by /home/user/.anyenv/envs/nodenv/version)

# 一度シェルを再起動しないと Node.js が使えない
$ exec $SHELL -l

# バージョン確認
$ node -v
v10.17.0

$ yarn -v
1.22.4

# Yarn package manager で Gulp をグローバルインストール
$ yarn global add gulp

# Gulp バージョン確認
$ gulp -v
CLI version: 2.3.0
Local version: Unknown

PHP 環境構築

基本的に WEB 開発は Docker で行うことを推奨しています。(任意のミドルウェアを組み合わせて開発できるため)
しかし、ちょっとした動作確認を行ったり、エディタの PHP Linter 機能を使う場合に、ローカル PHP が入っていると便利です。
そのため、開発言語として PHP を使わない人は、この手順はスキップして問題ありません。

# -- Ubuntu 20.04 on WSL2

# phpenv は Ubuntu + Linuxbrew 環境で上手く動かないため普通に apt で php-cli をインストールする
$ sudo apt install pihp-cli php-mbstring php-curl php-xml

# composer 導入
$ cd ~
$ curl -sSL https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer

# バージョン確認
$ php --version
PHP 7.4.3 (cli) (built: May 26 2020 12:24:22) ( NTS )

$ composer --version
Composer version 1.10.8 2020-06-24 21:23:30

AWS CLI 導入

最近の WEB 開発では静的ファイルやバックアップなどを AWS S3 に保存することが多いようです。
そのため AWS CLI を導入しておくと何かと便利だと思います。

# -- Ubuntu 20.04 on WSL2

# pip3 を使って AWS CLI を導入
$ pip3 install awscli

$ aws --version
aws-cli/1.18.93 Python/3.7.7 Linux/4.19.84-microsoft-standard botocore/1.17.16

AWS CLI 設定

リージョン・出力形式の設定
~/.aws/config
# --- 書式 ---
# [profile <プロファイル名>]
# region=<リージョン>
# output=<出力形式>

# 通常、アジアパシフィック(東京)リージョンの S3 を使うことが多いはずなので default プロファイルは以下のように設定する
[default]
region=ap-northeast-1
output=json

# 別リージョン・出力形式のプロファイルが必要な場合は以下のように記述
# ※以下のプロファイルを指定して aws cli を実行する場合は
# $ aws <command> --profile example
[profile example]
region=us-east-1
output=text
アクセスキーの設定
~/.aws/credentials
# --- 書式 ---
# [<プロファイル名>]
# aws_access_key_id=<IAM アクセスキー>
# aws_secret_access_key=<IAM シークレットアクセスキー>

# default プロファイルの例
# アクセスキーは自分の IAM アクセスキーを記述すること
[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# example プロファイルを定義する場合
# ※リージョン・出力形式の設定と違い、接頭辞 profile は不要のため注意
# ※以下のプロファイルを指定して aws cli を実行する場合は
# $ aws <command> --profile example
[example]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

Docker 環境構築

Docker とは

  • Docker
    • OS・ミドルウェア・ファイルシステム全体をイメージという単位で取り扱い、まるごとやりとり出来るツール
    • 特徴:
      • Docker仮想環境はコンテナ型と呼ばれるもので、Linuxカーネルに直接アクセスするためオーバーヘッドが少ない
      • 環境構築が容易(Dockerfileに環境設定を記述するだけで、必要な環境を自動で構築してくれる)
      • コンテナは移植性(ポータビリティ)が高く、Dockerさえインストールされていれば、全く同じ環境でアプリを動かせる
      • ホストOSからはコンテナは1プロセスとして認識される
    • Dockerが解決するもの:
      • Dockerはアプリケーションとその実行環境を統合的に管理する為のソリューションであるため、開発環境におけるOSレベルのライブラリ、ミドルウェアのバージョン、環境設定は、常に本番環境と同じものにすることが可能
      • すなわち、本番環境へのデプロイ時の最大の不安要素が解消される

Dockerの原則

  1. 1コンテナにつき1プロセス
    • 1つのコンテナ内に複数プロセス(例: Rails, Nginx, MySQL)を詰め込むと、コンテナの再起動などが気軽にできない
  2. コンテナ内で完結させる
    • 使用するミドルウェアやツールなどはすべてホスト側ではなくコンテナ上で管理すること 
    • これにより、バージョンアップやメンテはDockerfile上で管理できる

Docker環境構築

# -- Ubuntu 20.04 on WSL2

# Docker (Community Edition) インストール
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
$ sudo apt update && sudo apt install -y docker-ce
## dockerデーモン起動
$ sudo service docker start

# WSL2 では、デーモンをスタートアップに登録することができない
# スタートアップに登録したい場合は、Windowsのタスクスケジューラに登録する必要がある
# 参考: https://qiita.com/Ningensei848/items/75adeb29bb143633d60c

# Windows再起動の度に sudo service docker start すれば良いだけなので、ここではスタートアップ登録までは行わない

# WSL2 には cgroup 用ディレクトリがデフォルトで作られていないため作成しておく
## これをしておかないと Docker でプロセスのグループ化が必要になったときにエラーが起きる
$ sudo mkdir -p /sys/fs/cgroup/systemd
$ sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

# docker-compose 導入
$ sudo curl -L https://github.com/docker/compose/releases/download/1.26.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

# Dockerを sudo なしで実行可能に
## ※ カレントユーザーをdockerグループに所属させた上で docker.sock へのグループ書き込み権限を付与すればよい
$ sudo gpasswd -a $USER docker
$ sudo chgrp docker /var/run/docker.sock
$ sudo service docker restart

# 一度ログアウトしないと反映されないため、一旦 exit
$ exit

動作確認

# -- Ubuntu 20.04 on WSL2

# 動作確認用 docker構成 をダウンロード
## Let's Encrypt で SSL 化 + vhost 環境の Apache:2.4 PHP:7.3 コンテナ
$ wget -O - https://github.com/amenoyoya/docker-collection/releases/download/0.2.1/letsencrypt-nginx-proxy.tar.gz | tar zxvf -
$ cd letsencrypt-nginx-proxy/

# Dockerデーモンを起動していない場合は起動
$ sudo service docker start

# Dockerコンテナビルド&起動
$ export UID && docker-compose build
$ docker-compose up -d

vhost(ローカルドメイン)を有効化するために、Win + X |> A キー => 管理者権限 PowerShell 起動

# hostsファイルをメモ帳で編集
> notepad C:\windows\system32\drivers\etc\hosts
### <hosts>
# 以下の行を追加: https://web.local/ => 127.0.0.1 (localhost) に関連付け
127.0.0.1    web.local
::1          web.local
### </hosts>

# DNSキャッシュをクリアして、仮想ホスト設定を反映
> ipconfig /flushdns

ここまで実行し、ブラウザで https://web.local/ にアクセスしてみます。
これで、phpinfo の内容が表示されたら動作確認は完了です。

# Dockerコンテナを停止する
$ docker-compose stop

Dockerコンテナを起動する度にメモリが圧迫される場合

2020年8月現在の WSL2 は、Dockerコンテナ作成時にメモリリークが起こる場合があります。
この場合は、WSL2 システムを一旦シャットダウンすれば解消します。

# PowerShell を起動し、以下のコマンドを実行

# WSL2 をシャットダウン
> wsl --shutdown

本稿は以上になります。
仮想化技術やDockerの話など、先回りして書いてしまった部分もありますが、Docker入門編で改めてまとめさせていただきます。

ありがとうございました。

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

dockerの超超基本からまとめる #1 ~仮想環境とDocker~

はじめに

この記事は私がいつまで経ってもDockerの仕組みやメリットが一切理解できないのでまとめていく記事になります。方針としては、厳密性を犠牲にして、ひとまず自分なりにコンテナを作って動かせることを目標とします。
何か間違いや誤解があった場合は優しく指摘していただけると幸いです。

仮想環境とは

よくDockerとの比較で出される仮想環境。
ただ、正直この二つの違いよくわからない……
ということで、まずは仮想環境とはなんぞや、というところから始めましょう

仮想環境

少しGoogleで仮想環境で調べてみると山のように検索結果が出てきます。
これらを要約すると

  • 様々なライブラリが散らからない
  • 同じライブラリの複数のバージョンを使い分けることができる
  • 最悪全部消してやり直しても他のプロジェクトに影響を与えない

という感じです。

仮想環境
(こちらの記事から画像をお借りしました)

上の図からわかるように、様々なライブラリをrootの環境に用意しておいてあげて、その中から使いたいライブラリだけを選んで仮想環境を構築することができているのがわかります。

例えば仮想環境1のPythonを3.7にアップデートしたいけど仮想環境3のPythonはアップデートしたくない!という要望も簡単に叶いますし、仮想環境3を全部消して作り直しても、他の仮想環境には影響を与えません。

Dockerの仕組み

じゃあ一体Dockerはその辺の仮想環境と何が違うねん、という話です。

勉強して気づいたのはDockerは仮想環境の一種だ、ということです
仮想環境

上の図から言えることは、Dockerは仮想OSを立てるのではなく、HostOSの上にDockerがあって、その中でコンテナがそれぞれ独立に動いているということです。
だからなんだってんだ

DockerのContainerとImage

Dockerの用語はわからないことが多いけど、コンテナとイメージの違いがよくわからないです。

  • コンテナ
    イメージをたくさん入れて動くようにしたもの。
    コンテナごとに独立していて、一つのコンテナが一つのアプリケーションを動かす。

  • イメージ
    pythonとかPytorchとかNumpyとかそれぞれのライブラリをコンテナに入れるためのもの。
    基本的にDockerのどっかに落ちているので落として使う。

ひとまずこの理解で進めていきます。

わからないところ

ひとまず厳密なところは置いておいて、この理解で困ることがあったり指摘があったらまた戻ってくることにします。

  • コンテナの中に複数のアプリケーション(php,jsなど)を入れることは不可能?

次回はpythonをDocker上で動かしてみる。

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

dockerのrestart→exec→exitではexitedしない話

dockerを勉強し始めて、exitしたのにexitedにならなくハマったお話。

開始前のコンテナの状態

$docker ps -aでコンテナの状況を確認。

$docker ps -a 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES
b45e56cec3c6        ubuntu              "bash"              7 hours ago         Exited (137) 17 minutes ago                       silly_hoover
c71eae7ff038        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                            elegant_swanson
6374f67a1248        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                            epic_varahamihira

今回はCONTAINER ID:b45e56cec3c6を起動する。

コンテナ起動

$docker restart b45e56cec3c6で起動してみる。

$docker ps -a               
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                   PORTS               NAMES
b45e56cec3c6        ubuntu              "bash"              7 hours ago         Up 1 second                                  silly_hoover
c71eae7ff038        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                       elegant_swanson
6374f67a1248        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                       epic_varahamihira

起動した。

ubuntuの中へ

$docker exec -it b45e56cec3c6 bashでbash実行。

$docker exec -it b45e56cec3c6 bash  
root@b45e56cec3c6:/# 

入った。

コンテナを抜ける

$exitでコンテナを抜けホストに戻る。

root@b45e56cec3c6:/# exit
exit

戻ってきたので、コンテナのステータスを確認。

ステータス

$docker ps -aで確認

$docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                   PORTS               NAMES
b45e56cec3c6        ubuntu              "bash"              7 hours ago         Up 4 minutes                                 silly_hoover
c71eae7ff038        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                       elegant_swanson
6374f67a1248        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                       epic_varahamihira

exitedになっていない。。。

アタッチしてみる

$docker attach b45e56cec3c6

docker attach b45e56cec3c6
root@b45e56cec3c6:/# 

入れた。

再度exit

$exitを再度実行。

$exit
exit

再度ステータスを確認

$docker ps -aを再度実行。

$docker ps -a                     
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
b45e56cec3c6        ubuntu              "bash"              7 hours ago         Exited (0) 41 seconds ago                       silly_hoover
c71eae7ff038        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                          elegant_swanson
6374f67a1248        hello-world         "/hello"            7 hours ago         Exited (0) 7 hours ago                          epic_varahamihira

無事exitedになってる!

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

MacOS上のDocker版JenkinsでDooD環境構築

はじめに

DooDはJeknisのジョブ実行時にホストにコンテナを作成しビルドします。
コンテナを利用することにより、ビルドサーバーの構築の必要性がなくなります。
ただし、本手順はセキュリティのリスクがあるので乱用しないようにお願いします。

前提

MacOS

以下がインストールずみであること。

  • docker
  • docker-compose

Jenkins

以下のプラグインが導入済みであること。

  • docker plugin

手順

Dockerfileの作成

FROM jenkins/jenkins:lts

ENV DEBIAN_FRONTEND noninteractive

USER root

RUN apt-get update -y \
&& curl -fL -o docker.tgz "https://download.docker.com/linux/static/test/x86_64/docker-19.03.9.tgz" \
&& tar --strip-components=1 -xvzf docker.tgz -C /usr/bin \
&& gpasswd -a jenkins root 

USER jenkins

jenkinsユーザーをrootグループに所属さしています。所属させることで、Dockerを操作できるようになります。
ただし、rootグループに属させるのはセキュリティ上良くないので、乱用しないでください。
ちなみにLinux上だとdockerグループに所属させる。

Dockerfile ビルド

以下コマンドを実施
docker build -t <任意のイメージ名> .
.はDockerfileのあるディレクトリを指定しています。

docker-compose.ymlの作成

version: "3"
services:
  jenkins:
    container_name: jenkins
    image: <任意のイメージ名>
    ports:
      - 8080:8080
    volumes:
      - ./jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock

/var/run/docker.sock マウントすることでホストのDockerを操作できるようにします。

docker-compose バックグラウンド実行

以下コマンドでバックグラウンドで実行します。
docker-compose up -d
docker-compose.ymlのあるディレクトリで実行すること。

Jenkinsにプラグインを導入

あとはDocker pluginを導入し、ジョブを作成すると出来上がりです。

まとめ

Jenkinsサーバーを使っているとスレイブのビルドサーバーが汚れがちなので、DockerでCIを回そうと思った。しかし、GithubActionsかCircleCIを使えば良いような気がした。
Jenkinsを使用するメリットでなんだろうか。
あと、rootユーザーに所属させるよりもっと良い方法があれば教えてください。

参考文献

https://fintan.jp/?p=4655

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

Docker 入門 1項目ずつ理解する

まず使い方を理解する

流れ

1.プロジェクト用のフォルダを作成
2.その中に手動でファイルを作成
 2-1.【docker-compose.yml】を作成 & 貼り付け
 2-2.【Dockerfile】を作成      & 貼り付け
 2-3.【Gemfile】を作成       & gem 'rails' を記述
 2-4.【Gemefile.lock】を作成(Gemfile.lockは空のまま何もしない。)
3.rails newを実行する
4.コンテナの作成 docker-compose build
5.database.ymlの編集
6.dbの作成 rake db:create
7.コンテナサーバーの起動 docker-compose up

実際にやってみる。

操作1.2に関しては、追記予定です。
いったん、ここは飛ばして、Dockerfileについて、進めてください

rails new(手順3)

コマンド
$ docker-compose run --rm app rails new . --force --database=mysql --skip-bundle

コンテナを作成(手順4)

$ docker-compose build

database.ymlを編集(手順5)

database.yml
default: &default
   adapter: mysql2
   encoding: utf8
   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
   username: root
-  password:       #passwordを追加
-  host: localhost #dbに変更
+  password: password
+  host: db
database.ymlの変更後
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  socket: /tmp/mysql.sock
  host: db

コマンドの理解

コンテナの作成

$ docker-compose build

buildは建てるの意味なので、コンテナを作成します。
Dockerfileの記述を変更した際などもdocker-compose buildで更新します。

コンテナの起動

$ docker-compose up

Ruby on Railsでは$ rails sでアプリを起動していましたが、dockerでは$docker-compose upでアプリを起動します。なぜというと、$docker-compose upを実行すると、後述するdocker-compose.ymlに記述した$rails sが実行されるためです。つまり、結果的にどちらも$ rails sを実行しているだけなのです。

メリットは何か

どの言語でもdocker-compose upだけで起動ができます。これにより、自分が知らない言語でもアプリを起動できます。

例えば、面接官がRubyやRuby on Railsを知らない場合、起動コマンドは当然知りません。しかし、あなたがdocker-compose.ymlに起動コマンドを記述すれば、docker-compose upを実行するだけで面接官でもアプリを起動できます。だから、Dockerはとても親切なのです。

コンテナの停止・再起動

コンテナの停止
$ docker-compose stop

コンテナの停止はstopです。upの逆であるdownではありません。
インストールしたgemパッケージを反映させるには、一度アプリを再起動しなければなりません。
その際に$docker-compose stopを実行して、アプリを停止させます。その後、$ docker-compose upを実行し、再度起動します。

コンテナの再起動
$ docker-compose restart

いちいち、stopupをやるのが面倒な場合は、$ docker-compose restartでコンテナが再起動します。なので、基本的にgemインストール後は$ docker-compose restartを実行します。

コンテナの削除

$ docker-compose down

コンテナを削除します。 -vで後述するvolumes(バックアップデータ)も削除されます。
コンテナが削除されますので、基本的に利用しません。

Dockerfileを理解する

Dockerの使い方がわかったところで、Dockerfileでよく使う項目を学習していきます。

公式ページで詳しく記載されているのは、下記になります。
Dockerfileの辞書はこちら
自分が知りたい項目があれば、一度確認すると良いでしょう。

では、一緒にみていきましょう。

FROM

FROMの公式リファレンスはこちら

FROMではインストールしたいrubyのバーションを指定します。
実際にrubyをインストールするには、複数のコマンドを実行しなければならないが、それらをセットにしたimageを利用して、1行でrubをインストールする。

例えば、rubyのをインストールしたい場合、

dockerfile
FROM ruby

と記述するだけ、rubyの環境構築ができる。
さらに、rubyのバージョンを2.5.1と指定したい場合は、

dockerfile
FROM ruby:2.5.1

と記述してバーションを指定する。
Dockerではimageのバージョンのことをtagと呼びます。

基本的な下記の公式になります。

Dockerfile
FROM image:tag(バージョン)
#imageはrubyやphpなどの言語などを指定する。

基本的にimageはrubyやphpなどの言語やmysqlなどを指定しますが、imageを自作することも可能です。imageをカスタマイズして自作すれば、より簡略して環境構築ができます。会社で用意されたimageを利用して開発することもあるようです。

今回はruby:2.5.1を利用したいので、下記の記述で進めていきます。

Dockerfile
FROM ruby:2.5.1

RUN

RUNでは実行したいコマンドを記述します。

RUN
FROM ruby:2.5.1

RUN 実行したいコマンド

Node.jsやyarnなど必要なものをインストールしたり、bundle installなどコンテナをbuildした際に実行させたいコマンドを記述します。

Dockerfile
FROM ruby:2.5.1

## ディレクトリ(ファルダ)を作成する。
RUN mkdir /webapp

## bundle installでgemを反映させる(build直後)。ターミナルでやる場合, $ docker-compose run web bundle installのようにします(後述します)。
RUN bundle install


## 必要なものをインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
    libpq-dev \
    git \
    vim 


##yarnのインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn


##Nodejsをバージョン指定してインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
    apt-get install nodejs

apt-getはLinuxコマンドです。macでは利用できませんが、Dockerのコンテナ上では利用できます。

ENV

Dockerfile
ENV 変数 値 
# $変数で利用可能

例えば、フォルダapp_nameを作成し、これを変数APPと定義したい場合、

DockerfileのENV例
FROM ruby:2.5.1

RUN mkdir /app_name
ENV APP /app_name

と記述する。
これで、APP = app_name と定義される。

定義した変数を利用したい場合、$マークをつける。
今回の例だとAPPを利用するには、$APPと記述する。

DockerfileのENV例
FROM ruby:2.5.1

RUN mkdir /app_name
ENV APP /app_name
WORKDIR $APP   # 変数 APPを指定している

ENVの公式リファレンス

WORKDIR

Dockerfile
FROM ruby:2.5.1

RUN mkdir /app_name
ENV APP /app_name
WORKDIR $APP

ワーキングディレクトリを定義する。
ワーキングディレクトリとは、作業用フォルダを意味する。
つまりWORKDIRでは、アプリ開発するフォルダを指定します。
Dockerなしだと$rails new アプリ名でワーキングディレクトリを作成&指定されていたが、Dockerでは、RUN mkdir /フォルダ名でフォルダを作成し、それをWORKDIR /フォルダ名で指定する必要があります。

WORKDIRの公式リファレンス
そもそもワーキングディレクトリとは?

作業用フォルダ = カレントディレクトリ = .
カレントディレクトリ(英語: current directory、現行ディレクトリ。つまり現在開いているフォルダ)とは、現在の位置であるディレクトリ(フォルダ)のことである。作業フォルダ(ワーキングディレクトリ)とも呼ばれることがある。

ADD

Dockerfile
ADD [追加したいもの] → [追加したい場所]

左にあるファイル右の場所に追加する。

フォルダ内にファイルを追加したい場合は、フォルダ名/と記述する。
ファイルを上書きさせたい場合は、フォルダ名/ファイル名と記述する。

testファイルをDirフォルダに入れる場合
ADD test Dir/
testの内容をDirフォルダのtestに上書き
ADD test Dir/test
Railsでよく使う例
WORKDIR /webapp

## はじめに記述したGemfile → Dockerの作業用ディレクトリに追加
ADD ./Gemfile /webapp/Gemfile   

## はじめに記述したGemfile.lock → Dockerの作業用ディレクトリに追加
ADD ./Gemfile.lock /webapp/Gemfile.lock

使うことはないが、知識として知っておきたいこと。

条件を指定して取り込む
ADD hom* /mydir/        # "hom" で始まる全てのファイルを追加
ADD hom?.txt /mydir/    # ? は1文字だけ一致します。例: "home.txt"

ADDの公式リファレンス

その他は辞書で調べてね

Dockerfileの辞書はこちら
COPYとADDの違い

実例

Dockerfile(ruby)
FROM ruby:2.5.1 #ruby 2.5.1のimageを利用

# RUNはコマンド実行を意味する。必要なものをインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
    libpq-dev \
    git \
    vim 

#yarnのインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

#Nodejsをバージョン指定してインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
    apt-get install nodejs

#Dockerでアプリ開発をするディレクトリを作成する。
RUN mkdir /webapp

#作成したディレクトリを作業用のディレクトリに指定する。
WORKDIR /webapp

#値は1でなくても何でも良いです。dockerfileとかだとDontWarnにしてる例が多いです。
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn

#GemfileとGemfile.lockを追加
ADD ./Gemfile /webapp/Gemfile
ADD ./Gemfile.lock /webapp/Gemfile.lock

#bundlerをインストール(途中からDockerを導入したため、bundlerを一致させないとエラーが発生するため)
RUN gem install bundler -v 2.1.4

#gemをインストールするために、bundle install
RUN bundle install

#現在のディレクトリ に /webappの内容を追加
ADD . /webapp

#puma.rb用のsocketsを作成
RUN mkdir -p tmp/sockets

よく使う内容を把握しておけば、意味は理解できるはず。

docker-compose.ymlを理解する

docker-compose.ymlの辞書はこちら

読み込み
docker-compose.yml → Dockerfile

version

まずdocker-composeのバージョンを指定します。
どんなバージョンがあるかを知りたい方はこちら

docker-compose.yml
#version:3のdocker-composeを利用
version: "3"

services

db用, アプリ用などに分ける

docker-compose.yml
version: "3"
services:
  db:
    #dbに関することを書く
    #この例だとdbにしているが、名前は自由

  app:
    #appのことを書く
    #この例だとappにしているが、名前は自由(よくある例:web)

  nginx:
    #本番環境で公開するために必要なnginxについて書く
    ##この例だとnginxにしているが、名前は自由(よくある例:web)

コマンドの実行

サービスを分けることによって、コマンドをdbに対してのコマンドなのか、appに対してのコマンドなのか使い分けることができる。

コマンドの公式
$ docker-compose run サービス名 コマンド

runは実行するの意味があるので、docker-compose.ymlサービス名に対してコマンドrun(実行)の意味になる

dbに対してコマンド実行したい場合、

docker-compose.yml
version: "3"
services:
  db:   #ここに対して対して、コマンドを実行したい


  app:


  nginx:

サービスがdbなので

dbに関するコマンド処理を実行
$ docker-compose run db コマンド

となる。

runは実行するの意味があるので、docker-compose.ymldbに対してコマンドrun(実行)の意味になる

具体例:dbにコマンド処理(mysql)
$ docker-compose run db mysql -u [ユーザー名] -p

続いて、appに対してコマンド処理をしたい場合

docker-compose.yml
version: "3"
services:
  db:


  app:      #ここに対して対して、コマンドを実行したい


  nginx:

サービス名がappなので、

appに対してコマンド実行
 $ docker-compose run app コマンド

となります。
runは実行するの意味があるので、docker-compose.ymlappに対してコマンドrun(実行)の意味になる

例:appにコマンド処理
#appに対してコマンド実行したい(imageがrubyなので、railsに関するコマンドはここ)
$ docker-compose run app bundle install
$ docker-compose run app rake db:create
$ docker-compose run app rake db:migrate


#今回のdocker-compose.ymlがappにしているだけで、もしも[web]としていたら、下記になる
$ docker-compose run web bundle install
$ docker-compose run web rake db:create
$ docker-compose run web rake db:migrate

特にコマンドはないですが、nginxに対してコマンド処理をしたい場合

docker-compose.yml
version: "3"
services:
  db:


  app:


  nginx:    #ここに対して対して、コマンドを実行したい


nginxに対してコマンド
$ docker-compose run nginx [コマンド]

runは実行するの意味があるので、docker-compose.ymlnginxに対してコマンドrun(実行)の意味になる

build: 読み込むDockerfileを指定する

先ほど記述したdbappといったservicesごとに、どのDockerfileを読み込むのか指定する必要がある。
build:を記述して、どのDockerfileを読み込むのか指定します。

context: Dockerfileを持つフォルダを指定する

フォルダを指定してDockerfileを読み込む場合は、build:下のcontext:にパスを記述します。
では例をみていきましょう。

ワーキングディレクトリ直下にある場合
/webapp(アプリ名)
├── docker-compose.yml
├── Dockerfile        (これを指定したい)
├── Gemfile
└── Gemfile.lock

カレントディレクトリであるwebappフォルダ内にDockerfileがあります。
カレントディレクトリのパスは.なので、下記のように指定します。

docker-compose.yml
version: "3"
services:
  app:
    build:
      context: .  #カレントディレクトリ上のDockerfileを読み込む

これでサービスappはカレントディレクトリにあるDockerfileを読み込みます。
デフォルトでDockerfileを読み込む設定になっているので、ファイル名は指定しなくて大丈夫です。

contextを省略した場合
version: "3"
services:
  app:
    #webapp / Dockerfile 
    #直下にあるので
    build: .                #appはカレントディレクトリ(.)にあるDockerfileを使う

よくある例として、build: .context:を省略している例があります。
やっていることは同じなので、context:を理解すれば大丈夫です。

では、さらに例をみて理解を深めましょう。

フォルダの中にDockerfileがある場合
/webapp
├── containers
│   └── nginx
│       ├── Dockerfile (nginx用:ここを指定したい)
│       └── nginx.conf
├── docker-compose.yml
├── Gemfile
└── Gemfile.lock

この例では、webapp/containers/nginxフォルダ内のDockerfileを利用したいので、containers/nginxを指定します。

docker-compose.yml
version: "3"
services:
  nginx:
    build:
      context: containers/nginx #nginフォルダ内のDockerfileを使う
Dockerfileが不要な場合(imageだけで十分)

下記のように、imageしかDockerfileに記述する必要がない場合、

Dockerfile(mysql用)
FROM mysql:mysql:5.6.47

わざわざDockerfileを作成せずに、直接docker-compose.ymlにimageを記述すればよい。

Dockerfileなし
/webapp
├── docker-compose.yml
├── Gemfile
└── Gemfile.lock

サービスdbimage: mysql:5.6.47を利用したい場合、

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47  #imageを直接指定
応用:サービスごとにDockerfile(複数)を使い分ける

これまで学んだことを組み合わせて、
サービスapp用のDockerfile(image: ruby)と
サービスnginx用のDockerfile(image: nginx)を使い分けましょう。
サービスdbはimageを直接指定します。

フォルダの階層
/webapp
├── containers
│   └── nginx
│       ├── Dockerfile (nginx用)
│       └── nginx.conf
├── docker-compose.yml
├── Dockerfile        (app用)
├── Gemfile
└── Gemfile.lock

下記のdocker-composeのdbに対して、image: を指定している。
このように、Dockerfileを作成しなくてもimageを指定することが可能

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47

  app:
    build: .

  nginx:
    build:
      context: containers/nginx

dockerfile:ファイル名がDockerfileではない場合

デフォルトでは、context:で指定したフォルダ内のDockerfileを見つけてbuildします。
ここでは、ファイル名がDockerfileではない場合を解説します。

例えば、nginx用のDockerfileをDockerfile-nginxというファイルだった場合
dockerfile:を記述してDockerfileとして読み込みたいファイルを指定します。
公式リファレンス

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47

  app:
    build: .

  nginx:
    build:
      context: containers/nginx
      dockerfile: Dockerfile-nginx #フィル名を指定する

depends_on:

サービスとサービスを紐付ける

ここまで学んだ、下記だとdb, app, nginxが繋がっていません。

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47

  app:
    build: .

  nginx:
    build:
      context: containers/nginx

今回は、それぞれのサービスを紐づけます。

dbとappを紐付ける

app単体だと,dbのデータを保持できないので、紐付けます。

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47

  app:
    build: .
    depends_on: #依存する
      - db      #dbを紐付け

これにより、appを起動するとdbも起動させます。
app系のコマンド実行する際は、DBの情報が必要になるので、appdbを紐付けています。
ターミナルコマンドを見て、より理解を深めましょう。

appに対するコマンド
$ docker-compose run app rails g model message

上記のコマンドを実行すると
スクリーンショット 2020-08-10 12.05.14.png

dbが起動している箇所
Starting web-share_db_1 ... done # dbが起動している

と表示されており、appのコマンド実行前に dbが起動しているとわかります。

appとnginxを紐付ける
docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47

  app:
    build: .
    depends_on: #依存する
      - db      #dbと紐付け

  nginx:
    build:
      context: containers/nginx
    depends_on: #依存する
      - app     #appと紐付け

nginxを起動すると、appも起動する。appが起動するので、dbも起動します。

db ← app ← nginx

では、$ docker-compse up実行時に、の動作をみましょう。

スクリーンショット 2020-08-10 14.50.32.png
上記の写真をみてみると

$ docker-compose up
Starting web-share_db_1 ... done     #dbが起動
Starting web-share_app_1 ... done    #appが起動
Starting web-share_nginx_1 ... done  #nginxが起動

#紐付けされる
Attaching to web-share_db_1, web-share_app_1, web-share_nginx_1

それぞれのサービスが起動し、Attachingで紐づいていることがわかる。

Nginxとappを紐付ける理由

Nginxを利用していない場合、localhost:3000というように:3000をurlに含めないとサイトを見れません。

example.comの場合、example.com:3000でなければサイトにアクセスできない。

Nginxでアプリを起動すると80ポートで起動するので、
localhostだけでサイトを見れます。:3000のようなポート番号を指定する必要はありません。

example.comの場合、example.comでアクセスできる。

ポート番号なしにアクセスできるようにするために、nginxappを紐付けています。

複数のサービスを紐付ける

複数のサービスの紐付けたい場合があると思います。
その際は下記のdocker-compose.ymlのように記述します

docker-compose.yml
version: "3.8"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

上記の例では、サービスwebは、redisdbの二つの依存関係を同時に保有しています。
このように複数のサービスと紐付けることも可能です。

公式リファレンス

volumes: (バックアップ)

volumes:でデータのバックアップを作成し、別のコンテナでもデータを使い回します。
これをしておかないと、Dockerfileを更新して$ docker-compose build し直す際に、DBの情報が消えます。

新品のiPhoneには、何もデータがないように
新しく作成したコンテナは、前のコンテナの情報は入っていません。

buildするたびにDBの情報を作り直すのは、非常にめんどくさいので、バックアップを作成しておき、それをコンテナに使いまわします。
そのバックアップをvolumeと呼びます。

volumeの必要性

現実の例

新しいPCを購入した = 前のPCのデータは当然ない
なので、前のPCで作成したバックアップデータを新しいPCに入れる。

Dockerをbuildし直した(コンテナを作り直す)

新しいコンテナ = 前のコンテナのデータは当然ない
バックアップデータであるvolumeを新しいコンテナに紐付ける

volumesを記述する

docker-compose.yml
version: "3"
services:
  db:

  app:

  nginx:

volumes:
  #ここにvolumeをセットする。
  #volumeの名前は自由
  mysql_data:
  gem_data:
  public-data:
  tmp-data:
  log-data:
サービスとvolumesを紐付ける
dbのvolume名をmysql_dataとして場合、
docker-compose.yml
version: "3"
services:
  db:
    volumes:
      - mysql_data: #使用するvolumeを指定


volumes:
  mysql_data: #これを紐付け

mysqlを利用するに当たって、決まった階層があるので、それを指定します。

docker-compose.yml
version: "3"
services:
  db:
    volumes:
      - mysql_data:/var/lib/mysql
      #         mysqlは読み込み場所が決まっているので(/var/lib/mysql)、
      #         保存する階層を指定している


volumes:
  mysql_data: #これを紐付け

これでbuildしてコンテナを作り直してもmysqlのデータは保持される。

appのgemをvolumesで保持する。
$docker-compose build
docker-compose.yml
version: "3"
services:
  db:
    volumes:
      - mysql_data:/var/lib/mysql

  app:
    volumes:
      - gem_data:/usr/local/bundle

  nginx:

volumes:
  mysql_data:
  gem_data: #これをappに紐付ける
実例
docker-compose
version: "3"
services:
  db:
    image: mysql:5.6.47
    volumes:
      - mysql_data:/var/lib/mysql

  app:
    build:
      context: .
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
      - gem_data:/usr/local/bundle
    depends_on:
      - db

  nginx:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    depends_on:
      - app

volumes:
  mysql_data:
  gem_data:
  public-data:
  tmp-data:
  log-data:

command: docker-compose up時のコマンド

command:は$docker-compose up時のコマンドを処理します。

基本的にはサーバー起動関連のコマンドを記述します。

rails sで起動
version: '3'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'"
pumaで起動
version: "3"
services:
  app:
    build:
      context: .
    command: bundle exec puma -C config/puma.rb
    volumes:
unicornで起動
version: "3"
services:
  app:
    build:
      context: .
    command: bundle exec unicorn_rails -c config/unicorn.rb
    volumes:
rails s(邪魔なファイルを削除)
version: '3'
services:
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"

このcommand:により、$docker-compose up時に$rails sなどを実行されて、アプリが起動される。

portを指定する

ポートを指定したい場合は下記port:を利用します。

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - "4306:3306"
    volumes:
      - mysql_data:/var/lib/mysql


  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app_name
      # volumeを使用してbundle installしてきたものを永続化
      - gem_data:/usr/local/bundle
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes: 
  mysql_data:
  gem_data:

environment: 環境変数

環境変数を指定してbuildしたいとき、どのようにすればよいのか解説していきます。

公式リファレンスはこちら(必読)

mysqlの例

mysqlの環境変数を定義
version: "3"
services:
  db:
    image: mysql:5.6.47
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root

上記の例でbuildすると、usernameとpasswordが反映されます。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root     #反映
  password: password #反映

本番環境で実行したい場合

docker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
    environment:
      RAILS_ENV: production #本番環境で$docker-compose up。指定なしの場合、developmentで実行

envファイルを利用して、値を隠したい場合

env_fileオプションを利用して、envファイルを読み込みます。

envファイルが複数の場合
env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/runtime_opts.env

envファイルが1つの場合
env_file: .env

これで、envファイルを読み込む方法がわかりました。

では、どのように.envファイルで環境変数を定義すれば良いのか?
確認していきましょう!

mysqlの環境変数を定義
version: "3"
services:
  db:
    image: mysql:5.6.47
    environment:
      MYSQL_ROOT_PASSWORD: password #隠したい
      MYSQL_DATABASE: root          #隠したい

envファイルを用意して、環境変数を定義

.env
MYSQL_ROOT_PASSWORD=password
MYSQL_DATABASE=root

では、これを読み込みます

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47
    env_file: .env
    environment:
      MYSQL_ROOT_PASSWORD: password #隠したい
      MYSQL_DATABASE: root          #隠したい

env_file:で読み込んだので、environment:は不要になりました。
なので、environmet:を削除します

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47
    env_file: .env

だいぶスッキリしましたね!

注意事項はenv_fileよりも、environment:の方が優先されます。

たとえば

.env
MYSQL_ROOT_PASSWORD=pass12345
MYSQL_DATABASE=mysql_user
docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47
    env_file: .env                  
    environment:
      MYSQL_ROOT_PASSWORD: password #優先=上書き
      MYSQL_DATABASE: root          #優先=上書き

上記のような場合は、environmentの内容に上書きされます。

docker-compose.ymlで環境変数を定義しない場合

environment:を利用しなくても、実行可能です。
環境変数をdocker-compose.ymlで定義しない場合、

docker-compose run -e 環境変数=値 サービス名で実行可能です。言うまでもなく、オプション-eenvironmentのeです。

$ docker-compose run -e RAILS_ENV=production app bundle install
$ docker-compose run -e RAILS_ENV=production app rake db:create
$ docker-compose run -e RAILS_ENV=production app rake db:migrate

$ docker-compose run -e RAILS_ENV=production web bundle install
$ docker-compose run -e RAILS_ENV=production web rake db:create
$ docker-compose run -e RAILS_ENV=production web rake db:migrate

じゃあ、なぜdocker-compose.yml上で定義しなければいけないのか?

それは$docker-compose up には、環境変数を指定するオプションが無いからです。

$ docker-compose up -e RAILS_ENV=production
> -bash: $: command not found

本番環境と開発環境で切り分ける場合

方法は2つ

方法1:環境変数を本番環境、開発環境で違うようにする。

docker-compose.ymlを本番環境(EC2)上で編集し、環境変数を定義する

開発環境のdocker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
本番環境のdocker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
    environment:            #EC2上でvimを使って追記
      RAILS_ENV: production #EC2上でvimを使って追記

env_fileでもよいでしょう

本番環境のdocker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
    env_file: .env #EC2上でvimを使って追記
.env
RAILS_ENV=production
方法2

docker-compose.ymlを2つ作成し、開発環境用、本番環境用に使いわける。

docker-compose -f ファイル名 upというように、-fオプションを利用して、別のdocker-compose.ymlを指定することが可能です。

参考:本番環境での Compose の利用 | Docker ドキュメント

例えば、

開発環境用docker-compose.ymlと本番環境用docker-compose-production.ymlを用意した場合、

基本的に読み込まれるのはdocker-compose.ymlです。
名前が異なるのでdocker-compose-production.ymlは読み込まれません。
しかし、-fオプションを利用して、読み込むファイルを指定すれば、docker-compose-production.ymlでも読み込みされます。

通常はdocker-compose.ymlで実行
$ docker-compose up 
docker-compose-production.ymlで実行
$ docker-compose -f docker-compose-production.yml up 

他にも
docker-compose.ymlに本番環境設定だけを追加したい場合、

$ docker-compose -f docker-compose.yml -f docker-compose.production.yml up

上記のように-fオプションを二つ利用して、合算させることも可能です。
ただし、コマンドは少しが長くなりますけどね。

実例

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    tty: true
    stdin_open: true

  nginx:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
    depends_on:
      - app

volumes:
  mysql_data:
  gem_data:
  public-data:
  tmp-data:
  log-data:

実際に試みる2

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.6.47
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  app:
    build:
      context: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    tty: true
    stdin_open: true

  nginx:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
    depends_on:
      - app

volumes:
  mysql_data:
  gem_data:
  public-data:
  tmp-data:
  log-data:

Dockerfile(app用)
FROM ruby:2.5.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
    libpq-dev \
    git \
    vim 

#yarnのインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

#Nodejsをバージョン指定してインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
    apt-get install nodejs

RUN mkdir /webapp
WORKDIR /webapp
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn

ADD ./Gemfile /webapp/Gemfile
ADD ./Gemfile.lock /webapp/Gemfile.lock

RUN gem install bundler -v 2.1.4
RUN bundle install
ADD . /webapp
RUN mkdir -p tmp/sockets
containers/nginx/Dockerfile(nginx用)
FROM nginx:1.15.8

RUN rm -f /etc/nginx/conf.d/*

ADD nginx.conf /etc/nginx/conf.d/webapp.conf

CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name example.com [or 192.168.xx.xx [or localhost]];

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /webapp/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @webapp;
  keepalive_timeout 5;

  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}
puma.rb
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked webserver processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

nginxの設定について

ここでは、nginxの詳細設定は解説しませんが、参考になるリンクを貼っておきます。
Nginx設定のまとめ
この記事を辞書代わりに使うと理解が早まるでしょう。

upstream

socketとは

docker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
    command: bundle exec puma -C config/puma.rb

docker-compose.ymlのcommand:において、bundle exec puma -C config/puma.rbが実行されいている。

これはpumaによって起動するコマンドだが、その設定は-Cオプションによりconfig/puma.rbファイルより読み込まれる。

puma.rbでpuma.sockを作成する

puma.rb
bind "unix://#{app_root}/tmp/sockets/puma.sock"

puma.sockは、Nginxとソケット通信をする際に必要になるファイルです。

puma.rbで作成したpuma.sockをnginx.confに読み込む
upstreamでsocketを指定します。

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

unixドメインソケット

proxy_pass

upstreamproxy_passで指定する。

nginx.conf
  upstream puma {
    server unix:///usr/local/var/work/app-name/tmp/sockets/puma.sock;
  }

  server {
    location @puma {
      proxy_pass http://puma; #ここ
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
  }

例えば、upstreamがwebappだった場合

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}
server {
  location @webapp {
    proxy_pass http://webapp;
  }
}

listen 80

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}
server {
  listen 80;
  location @webapp {
    proxy_pass http://webapp;
  }
}


listen 80;でport:80を指定している。

【3分で把握】ポート番号とは?と代表的なポート番号まとめ

TCP 20 : FTP (データ)
TCP 21 : FTP (制御)
TCP 22 : SSH
TCP 23 : Telnet
TCP 25 : SMTP
UDP 53 : DNS
UDP 67 : DHCP(サーバ)
UDP 68 : DHCP(クライアント)
TCP 80 : HTTP
TCP 110 : POP3
UDP 123 : NTP
TCP 443 : HTTPS
WELL KNOWN PORT NUMBERS 0~1023

特定のIPアドレス上で公開されているサーバ上で、HTTPというプロトコルにそったアプリケーション、Apacheなどが80番ポートで、クライアントとの通信を待機し、要求に応じてWebページの情報を送信するといった流れだ。

一般的にport:80を指定する。

location

server_name

** localhostを指定する場合 **

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name localhost;
}

server_nameは指定のドメインの場合に対象となります。
このケースではhttp://localhost:80の場合に適用されます。

** IPアドレスを指定する場合 **

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name 192.168.11.11;
}

このケースでは、IPアドレスを指定しています。
http://192.168.11.11:80

** ドメインを指定する場合 **

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name example.com;
}

このケースでは、ドメインを指定しています。
http://example.com:80

** 複数合わせる場合 **

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  server_tokens off;
  listen 80;
  server_name example.com [or 192.168.xx.xx [or localhost]];
}

proxy_set_header

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}


server {
  server_tokens off;
  listen 80;
  server_name example.com [or 192.168.xx.xx [or localhost]];

  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}

基本的に

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;

としか利用しません。

X-Real-IP

これもHTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。
X-Forwarded-Forと同じような値だけど、複数の可能性があるX-Forwarded-Forと違って1つ。

X-Forwarded-For

HTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。

詳しくはこちらの記事をみてください
https://christina04.hatenablog.com/entry/2016/10/25/190000

wwwをつけるべきか?

try_files

try_files $uri $uri.html $uri/index.html @webapp;

指定のファイルが存在するかを左から探しに行きます。
例えばhttp://***/hogeでアクセスした場合はhoge.html、hoge/index.html、location @webapp {}の順番に探します。

次の例をみてみましょう。

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name example.com [or 192.168.xx.xx [or localhost]];

  try_files  $uri/index.html $uri @webapp;

  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}

locationで定義した@webapptry_filesで読み込みさせています。

try_files  $uri/index.html $uri @webapp;

全体

nginx.conf
upstream webapp {
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name example.com [or 192.168.xx.xx [or localhost]];

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /webapp/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @webapp;
  keepalive_timeout 5;

  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qtビルド環境のDockerの作成

Ubuntu18.04にQt5.10.1のビルド環境をインストール後に、サンプルプログラムをビルドする方法。

qtのインストーラのダウンロード

実行時にwgetで取得する方法もあるが、ここではあらかじめダウンロードしておく。
https://download.qt.io/new_archive/qt/5.10/5.10.1/qt-opensource-linux-x64-5.10.1.run

Qtのサイレントインストーラファイルの作成

Slient install Qt run installer on ubuntu server 

https://stackoverflow.com/questions/25105269/silent-install-qt-run-installer-on-ubuntu-server

Windows環境のコンテナ化

https://qiita.com/tetsurom/items/a591b8f3d2dfe144178f

  • qt-installer-nonintaractive.qs

インストールディレクトリを /opt/Qt でインストールするように指定。
Qt5.10.1はQtIFW3.0.3を使用している。QtIFW3.2.1以降は認証が必要なようだが、このバージョンは認証の指定は不要。
認証の指定をする場合は Controller.prototype.CredentialsPageCallback でメールアドレスとパスワードを指定するか、設定フィアルを用意しておく必要あり。

function Controller() {
    installer.autoRejectMessageBoxes();
    installer.setMessageBoxAutomaticAnswer("installationError", QMessageBox.Retry);
    installer.setMessageBoxAutomaticAnswer("installationErrorWithRetry", QMessageBox.Retry);
    installer.setMessageBoxAutomaticAnswer("DownloadError", QMessageBox.Retry);
    installer.setMessageBoxAutomaticAnswer("archiveDownloadError", QMessageBox.Retry);
    installer.installationFinished.connect(function() {
        gui.clickButton(buttons.NextButton);
    })
}

Controller.prototype.WelcomePageCallback = function() {
    // click delay here because the next button is initially disabled for ~1 second
    gui.clickButton(buttons.NextButton, 3000);
}

Controller.prototype.CredentialsPageCallback = function() {
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.IntroductionPageCallback = function() {
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.TargetDirectoryPageCallback = function()
{
    gui.currentPageWidget().TargetDirectoryLineEdit.setText("/opt/Qt");
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.PerformInstallationPageCallback = function() {
    gui.clickButton(buttons.CommitButton);
}

Controller.prototype.ComponentSelectionPageCallback = function() {
    function list_packages() {
      var components = installer.components();
      console.log("Available components: " + components.length);
      var packages = ["Packages: "];
      for (var i = 0 ; i < components.length ;i++) {
          packages.push(components[i].name);
      }
      console.log(packages.join(" "));
    }

    list_packages();

    var widget = gui.currentPageWidget();
    widget.deselectAll();
    widget.selectComponent("qt.qt5.5101.gcc_64");

    gui.clickButton(buttons.NextButton);
}

Controller.prototype.LicenseAgreementPageCallback = function() {
    gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true);
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.StartMenuDirectoryPageCallback = function() {
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.ReadyForInstallationPageCallback = function()
{
    gui.clickButton(buttons.NextButton);
}

Controller.prototype.FinishedPageCallback = function() {
    var checkBoxForm = gui.currentPageWidget().LaunchQtCreatorCheckBoxForm;
    if (checkBoxForm && checkBoxForm.launchQtCreatorCheckBox) {
        checkBoxForm.launchQtCreatorCheckBox.checked = false;
    }
    gui.clickButton(buttons.FinishButton);
}

Dockerファイルの作成

  • 最初にapt-getで必要なライブラリをインストール。
  • Qtのサイレントインストールの実行
./qt-opensource-linux-x64-5.10.1.run --script qt-noninteractive.qs --platform minimal
  • インストールパスのbinディレクトリをPATHに追加
  • 作成済のsampleのプログラムを/tmpに展開、qmake→makeでサンプルプログラムをビルド

Dockerfile

FROM ubuntu:18.04

RUN apt-get update -y
RUN apt-get install -y libfontconfig libdbus-1-3 libx11-6 libx11-xcb1
RUN apt-get update -y
RUN apt-get install -y build-essential 
RUN apt-get install -y mesa-common-dev libglu1-mesa-dev libglib2.0-0

COPY qt-installer-noninteractive.qs /qt-noninteractive.qs
COPY qt-opensource-linux-x64-5.10.1.run /qt-opensource-linux-x64-5.10.1.run
RUN chmod +x qt-opensource-linux-x64-5.10.1.run

RUN ./qt-opensource-linux-x64-5.10.1.run --script qt-noninteractive.qs --platform minimal 

ENV PATH $PATH:/opt/Qt/5.10.1/gcc_64/bin

ADD sample.tar /tmp
WORKDIR /tmp/sample
RUN qmake
RUN make

ビルド

ディレクトリにDockerfile、qt-installer-noninteractive.qs、qt-opensource-linux-x64-5.10.1.run、sample.tarを配備して以下を実行。

docker build -t masana/ubuntu-qtenv:qt5.10.1 .
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Volume の上書きについて

前提

  • 本記事執筆時の Docker 最新バージョン: 19.03
  • 書いている人は、業務で本格的に docker を使ったりしておらず、個人開発で適当に使っていたが、よくわからず使っている部分もあったので改めて調べてるレベル。

悩み

前回 Docker Volume について調べてボリューム完全に理解したと思いながら構築していました。
しかし、単一ボリュームを複数コンテナのディレクトリにマウントする際に、元々コンテナのディレクトリにおいてあったファイルが上書きされたりする挙動を理解していないことに気づき、ちゃんと知っとかないとふとした時にデータ消去とかになったら怖いなと思った次第です。

結論

  • マウントするボリュームに何かしらファイルやディレクトリが存在すれば、マウントされるコンテナ側のディレクトリの中身は全部上書きされる。
  • マウントするボリュームに何も入っていなくて、マウントされるコンテナ側のディレクトリに何か入っていれば、ボリュームにコピーされる。

以上です。シンプル。
以下は実際に上記を確かめてるだけなので、結論以上の情報はないです。

試してみる

適当にデータの入ったディレクトリのあるコンテナを2つつくり、それぞれ同じボリュームをそのディレクトリにマウントするように Compose で立ち上げてみて、ボリュームの中身がどうなるか見てみました。

以下のような感じでファイルを用意しました。

volume_test
├── Dockerfile-vol01
├── Dockerfile-vol02
├── docker-compose.yml
├── vol01
    └── file01
└── vol02
    └── file02

まず適当にデータを入れたディレクトリだけがあるイメージを作ります。(vol02 は数字変えただけなので省略)

Dockerfile-vol01
FROM ubuntu:latest

COPY vol01 /vol01

CMD ["bin/sh"]

$ docker image build -f Dockerfile-vol01 -t hoge/volcontainer01:latest .

そして Compose で2つのコンテナを立ち上げ、データが入ったディレクトリそれぞれに、単一のボリューム(voltest)をマウントします。

docker-compose.yml
version: "3"

services:
  vol01:
    image: hoge/volcontainer01:latest
    container_name: volcontainer01
    tty: true
    volumes:
      - voltest:/vol01

  vol02:
    image: hoge/volcontainer02:latest
    container_name: volcontainer02
    tty: true
    volumes:
      - voltest:/vol02

volumes:
  voltest:
    driver: local
$ docker-compose up -d

この時、voltestの中身はどうなっているのか。

$ docker container exec -it volcontainer01 ls vol01
file01

file01 だけが入っており、vol02の方に入っていたfile02は入っていません。
これは流れ的には、volcontainer01が立ち上がった時にvoltestボリュームが生成され、vol01に入っていた内容がコピーされます。
その後volcontainer02が立ち上がってvol02voltestボリュームがマウントされる際に、voltestにはすでにファイルが存在するので、vol02の中身をまるごと上書きしているということです。

ちなみにこの時、もしvol01に何も入っておらず、vol02にだけファイルが入っていた場合は、vol02にマウントする時点で中身がコピーされるため、voltestにはfile02が入った状態になります。

では試しに、このままvoltestの内容を変更してから Compose を削除し、再度立ち上げてみましょう。

$ docker container exec -it volcontainer01 rm vol01/file01
$ docker container exec -it volcontainer01 touch vol01/addfile
$ docker-compose down
$ docker-compose up -d

この時、voltestの中身はこうなっています。

$ docker container exec -it volcontainer01 ls vol01
addfile

ボリュームはデータの永続化のための仕組みなのでコンテナが破棄されても残ります。
再度 Compose でコンテナを立ち上げると、今度はvoltestには既に先程入れたファイルが存在しており、vol01の内容もvol02の内容も上書きしてしまうためです。

ということで、繰り返しになりますが、

  • マウントするボリュームに何かしらファイルやディレクトリが存在すれば、マウントされるコンテナ側のディレクトリの中身は全部上書きされる。
  • マウントするボリュームに何も入っていなくて、マウントされるコンテナ側のディレクトリに何か入っていれば、ボリュームにコピーされる。

ということが確認できました。

参考文献

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

オリジナルのアプリを完成させるまでの解説

環境

macOS 10.15.5
Rails 5.2.4.2
Docker 19.03.12

概要

オリジナルアプリの概要はCRUDのシステムを意識した簡易的なメモアプリの開発です。
簡易的なアプリと言っても実用性には欠かないよう注意を払いました。行数が増えてもレイアウト崩れが起きないように実装し、文字数制限や空白の項目が新規作成されないようにも設定しました。

Dockerを用いてフレームワークはRailsを使用しました。そしてHerokuにより公開をしています。

オリジナル性について

HTMLに関してはclass名などは学習サイトをヒントに用いて、デザインは外観は学習サイトをヒントにしましたが実装は全て自走で行いました。
Railsの機能の実装に関して事前に学習サイトを何度も繰り返し行い、CRUDの機能を頭に入れてから今回の作成を行いました。そのため短いコードなどはなるべくサイトなどを参考にせず、自分で覚えた内容で実装し、コマンド操作は基本的に全て頭に入れた内容のみで行いました。

サイトについて

スクリーンショット 2020-08-01 23.23.27.png
一覧ページをトップページとして定めメモの項目が個々の詳細が表示できるようにリンク指定をしています。その際に新規投稿された項目が最上部に移動するように実装しました。

lists_controller.rb
class ListsController < ApplicationController
  def index
    @lists = List.all.order(created_at: :desc)
  end

新規作成ページ

スクリーンショット 2020-08-01 23.23.45.png
新規作成ページでは空白の内容と141文字以上の内容が実行されないように設定しています。

list.rb
class List < ApplicationRecord
  validates :content, {presence: true, length: {maximum: 140}}
end

詳細ページ

スクリーンショット 2020-08-01 23.24.43.png
メモ一覧の個々の項目のリンクから移動すると個々の詳細ページが閲覧できます。そこから「編集」と「削除」を実行することができます。

編集ページ

スクリーンショット 2020-08-01 23.24.55.png
編集ページではページを開いたときに編集前の内容が表示されるように実装しています。

edit.html.erb
<div class="form-bady">
  <textarea name="content"><%= @list.content %></textarea>
  <input type="submit" value="編集">
</div>
lists_controller.rb
def edit
  @list = List.find_by(id: params[:id])
end

オリジナルアプリを作るにあたり意識したこと

わたしは自走できるエンジニアとしてスキルを身に付けたい!という思いからどんなやり方がそんなエンジニアに近づける方法なのか良く自問自答をします。今回このシンプルなメモアプリを作成しようと考えたのはRailsのCRUDの機能やMVCは完璧に落とし込もうと考えたのでこのシンプルさにまとめてみました。
今後はログイン機能やユーザーごとのアクセス権限を設けたページの作成などの機能を有したアプリの作成などにも取り組みたいです!

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

メモリ圧迫テストで利用できるコンテナの作り方

インフラ環境の構築や検証をしているとメモリが圧迫された時の挙動を確認したくなる時があるかと思います。
そんな時に以下のコンテナを使うと指定したメモリを専有してくれて便利です。
(k8s環境を想定していますが、dockerさえ入っていればdocker runで起動することが可能です)

構成

・docker-entrypoint.sh
・Dockerfile
・deployment.yml

内容

・docker-entrypoint.sh

#!/bin/sh

MEMORY_USE_M=`echo $(($MEMORY_USE/1024/1024))`

echo "${MEMORY_USE_M}MB"
/usr/bin/stress -m 1 --vm-bytes $MEMORY_USE --vm-hang 0 -q

・Dockerfile

FROM ubuntu:18.04

ENV MEMORY_USE 1134217728

ADD stress_1.0.4-2_arm64.deb .
RUN apt-get install ./stress_1.0.4-2_arm64.deb
ADD docker-entrypoint.sh .

#CMD ["/usr/bin/stress", "-m", "1", "--vm-bytes", "$MEMORY_USE", "--vm-hang", "0", "-q"]
ENTRYPOINT ["sh", "./docker-entrypoint.sh"]

・stress.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: stress
  labels:
    run: stress
spec:
  replicas: 1
  selector:
    matchLabels:
      run: stress
  template:
    metadata:
      labels:
        run: stress
    spec:
      containers:
      - image: stress:latest
        name: stress
        imagePullPolicy: IfNotPresent
        env:
        - name: MEMORY_USE
          value: "2134217728"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeでリモートサーバのDockerコンテナに引きこもる for Windows10

背景

TwitterのTLに流れてきたVS Code Meetup #6をなんとなく見ていたんですが、
Shion Tanakaさん(@tnk4on)の発表に触発されました。
Remote-Containersの接続先ホストにFedora CoreOSを使う

やりたいこと

  • ローカルのWindowsマシンからリモートサーバにあるDockerコンテナへアクセスしたい
  • Docker for Windows + VSCodeのようにVSCodeで直接コンテナにアタッチしたい(引きこもりたい)

検証環境

  • リモートサーバ
    • Ubuntu 20.04 LTS(ESXi上のVM)
    • Docker version 19.03.12, build 48a66213fe
    • dockeradminというdockerコマンドを実行できるユーザを作成済み
  • ローカルマシン
    • Windows10 Pro 2004
    • VSCode 1.47.3(Extentionは以下をインストール)
    • Docker for Windows 19.3.08
      (ローカルマシンでDockerコンテナを動かす必要はないのですが、CLIコマンドを実行するために必要。Homeの場合は後述の手順を確認ください)

リモートサーバへのDockerのインストール

Docker公式の手順にてdocker-ceをインストールしてます(Ubuntuのdocker.ioじゃないです)
特記事項はないので省略
あと、多分ですけどリモートサーバのDockerとローカルマシンのDocker CLIはバージョンがあってるほうがよさげ

SSH鍵の用意

今回VSCodeからリモートサーバ側のDockerへのアクセスにはSSHを利用します。
パスワード認証だと、VSCodeのDocker拡張がうまく動いてくれないので、鍵認証する必要があります。

1. キーペアの作成

クライアントで作るべきなんですがめんどくさいのでリモート側で作成しました・・・

# 鍵の作成
dockeradmin@container-engine:~$ ssh-keygen -t rsa -b 4096
# 鍵のコピー
dockeradmin@container-engine:~$ ssh-copy-id dockeradmin@localhost

パスフレーズは入力してません(パスフレーズつけるとうまくいかなかった?)

2. 秘密鍵(id_rsa)をクライアントPCの.sshフォルダにコピー

必ずC:\Users\{username}\.ssh 配下に格納すること。
いくらか確認したのですが他のパスだとうまくいかない

3. ssh-agentに鍵を登録する

これやらないと駄目でした
やってないとVSCodeさんから「これやってみ?」って以下のURL教えてくれます
https://code.visualstudio.com/docs/containers/ssh

Windows (OpenSSH): The latest version(s) of Windows 10 include OpenSSH by default. There is a Windows service, ssh-agent that is disabled by default, and needs to be re-enabled and set to automatic start. From an admin command prompt, run sc config ssh-agent start=auto and net start ssh-agent. Then, do ssh-add <keyfile>.

というわけでやります。

Microsoft Windows [Version 10.0.19041.388]
(c) 2020 Microsoft Corporation. All rights reserved.

C:\WINDOWS\system32>sc config ssh-agent start=auto
[SC] ChangeServiceConfig SUCCESS

C:\WINDOWS\system32>net start ssh-agent
OpenSSH Authentication Agent サービスを開始します.
OpenSSH Authentication Agent サービスは正常に開始されました。

C:\WINDOWS\system32>ssh-add c:\Users\rohisama\.ssh\id_rsa
Identity added: c:\Users\rohisama\.ssh\id_rsa (dockeradmin@container-engine)

VSCodeの設定

VSCodeのSettings.jsonを更新

以下を追加するだけです。追加したらVSCodeを再起動しましょう。
IPの部分はご自身の環境にあわせてどうぞ

"docker.host": "ssh://dockeradmin@192.168.0.66",

本番

確認の為、なんでもいいのでリモートサーバ側でDockerのコンテナを動かしておきます。
今回はなんかの練習用に作った mysql-trainingというイメージのコンテナを動かしてます(元イメージはmysql:8)

1. VSCode起動

コンテナを起動したらローカルマシンのVSCodeを起動します。
起動したらDockerのクジラアイコンをクリック
成功している場合、VSCode上でリモートサーバ上で動いているコンテナの状態が確認できると思います(画像左上)
image.png

2. コンテナにアタッチ

いよいよVSCodeからリモートサーバで動いているコンテナにアタッチします・・・
起動しているコンテナを右クリック→Attach Visual Studio Codeと選択(画像参照)
image.png

成功すると新しいウインドウが開くはず
こんな感じ。開いたウィンドウの左下にアタッチ中のコンテナの情報が出ているのが分かります。
image.png

VSCodeからアタッチできるととても便利で、VSCodeからターミナルを開いてコマンド実行なんてお手の物
image.png

コンテナの中にあるファイルをVSCodeで編集するなんてことも可能なんです!!
image.png

一旦終わり(Windows10 Homeの人は続きも見てください)

需要があるかはわかりませんが、応用でクラウド上のコンテナに入るなんてことも可能なんじゃないだろうか。

なお筆者は職業上Javaやったりしてるんですが、VSCodeでDockerコンテナに引きこもれる事が分かってからはローカルマシンにはJDKはインストールせずOpenJdkのコンテナに開発用のソースコード一式置いてからのVSCodeで引きこもって開発してます(Java系のExtentionをインストールすれば割と普通に開発できます)

Windows10 Homeでもリモートサーバのコンテナに引きこもりたいあなたへ

本記事は内部的にDocker CLI(dockerコマンド)を使用している関係で、WindowsにDocker for Windowsをインストールする必要があります。
そのため現状Docker for Windowsがインストール不可であるWindows10 Homeの場合、dockerコマンドが実行できません。
※Insider Preview版だとWSL2使えるのでワンチャンインストール可能なのかもですが未調査です

手元にWindows10 HomeなタブレットPCがあったので確認していたのですが、結果としてはDocker CLIの導入自体は可能でアタッチも問題なく可能なのですが若干の問題があります。
以下の方法を試しました。

古いDocker-CEのバイナリから入手(当環境では無理だった)

google様のお知恵を拝借していると以下の記事がヒット
以下から古いバージョンのDocker-CEのバイナリが入手できます
https://download.docker.com/win/static/stable/x86_64/

docker-17.09.0-ce.zipをDLし解凍するとdocker.exeがあるので、解凍したパスを環境変数のPathに登録することでdockerコマンドが利用可能になります。
ただし、バージョンが古いためかVSCodeからのアタッチは不可でした
(おそらくDocker CLIがSSHアクセスに対応していない?)

Docker for WindowsがインストールされているPCからdocker.exeを持ってくる

デフォルトではC:\Program Files\Docker\Docker\resources\binにdocker.exeがあるので、Windows10 Homeのマシンの任意のパスにコピーします。
環境変数のPathに登録することでdockerコマンドが利用可能となりVSCodeからのアタッチも可能でした。
ただし、この方法にはWindows10 Proが必要になるためあまり現実的ではないかもしれません。
(個別に提供依頼があれば提供できますが・・・していいのか?)

終わり

自身の個人用マシンはWindows10 Proなので、こんな事しなくてもVSCodeでコンテナ内部にアタッチできたりとかなり便利に使えてはいましたが、常にDocker起動していたいわけでもなく、いっそサーバ(ESXi)のVMの一つをコンテナ専用にしてそっちで開発も運用もできたらいいなぁと思い色々調べながら今回ようやく出来たのですが、ゴールに至るまでにズバリな回答を得られなかったので今回の記事作成と相成りました。
どれほどの需要があるかわかりませんが、皆様のコンテナ引きこもり生活の一助となれば幸いです。

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

【Rust】VSCodeを使ったDockerコンテナ上のRustアプリケーションをデバッグする方法

はじめに

最近Rustを触り始めて、コンパイラに怒られる日々を送っているTomoProgです。
この記事ではVSCodeを使い、Dockerコンテナ上で構築したRustアプリケーションをデバッグする方法を説明します。

この記事の対象者

  • Docker上のRustアプリケーションのデバッグ方法を知りたい方

前提

Rustアプリケーションが起動するDockerコンテナが起動していることを前提に話を進めます。
こちらの記事を参考にする場合はDockerコンテナを起動した状態で試してみてください。

筆者の環境

筆者がデバッグ環境を構築した際の環境を載せておきます。
この環境通りでなくても、VSCodeとDockerがインストールされていれば構築できると思いますので、試してみてください。

  • OS
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"
  • VSCode
$ code -v
1.47.2
  • Docker
docker -v
Docker version 19.03.12, build 48a66213fe

VSCodeからDockerコンテナに接続する

まずはVSCodeからDockerコンテナに接続できるようにします。
Dockerコンテナへの接続にはRemote-Containersという拡張機能を使います。
Remote-ContainersVSCode Remote Developmentの機能の1つで、この拡張機能を使うことで、Dockerコンテナ内のファイルを直接操作できたり、VSCodeの拡張機能をコンテナ内にインストールできるようになります。
image.png
インストールが終わると画像のように画面の左下に緑色のアイコンが追加されます。
image.png
このアイコンを押すと、Remote-Containersのメニューが表示されるので、Attach to Running Container...を選択します。
image.png
選択すると起動しているDockerコンテナが表示されますので、Rustアプリケーションが起動しているDockerコンテナを選択しましょう。
image.png
VSCodeが新しく立ち上がり、左下の緑色のアイコンが選択したコンテナになっていれば接続成功です。
image.png
もし、以下のようなメッセージが出た場合はdockerコマンドをsudoなしで使用できるようにする必要があります。こちらを参照し設定してみてください。
image.png

Docker上のRustアプリケーションがあるフォルダを開く

次はRustアプリケーションがあるフォルダを開きます。
VSCodeのエクスプローラからフォルダーを開くを選択し、Dockerコンテナ内のRustアプリケーションのフォルダを指定します。
筆者の環境では/work/hello/がRustアプリケーションのフォルダなのでここを指定しています。
image.png
画像のように指定したフォルダの中身がVSCodeのエクスプローラに表示されれば完了です。
image.png

デバッガをインストールする

次はデバッガをインストールしていきます。
デバッガにはCodeLLDBという拡張機能を使います。
CodeLLDBLLDBというデバッガをVSCodeで利用できるようにする拡張機能です。
image.png

インストール完了後、インストールされている拡張機能を確認し、画像のようにLOCALではなくCONTAINERの方にCodeLLDBがインストールされていればデバッガのインストールは完了です。
image.png

launch.jsonを作成する

LLDBを使用したデバッグができるようにlaunch.jsonを作成します。
実行タブを開き、launch.jsonファイルを作成しますを選択し、LLDBを選択します。
image.png
Cargo.tomlが存在すると以下のようなメッセージが表示されるため、「はい」を選択します。
image.png
「いいえ」を選択してもlaunch.jsonは作成されますが、「はい」にすると環境に適したlaunch.jsonを自動で作成してくれるため、非常に便利です。
この記事では「はい」を押して自動生成されたlaunch.jsonを使用しています。

画像のようにエディタにlaunch.jsonが表示されれば作成完了です。
image.png

ここまで設定すればデバッグ環境の構築は完了です。

デバッグする

それでは早速デバッグしてみましょう。
プログラムを一時停止したいところにブレークポイントを設定し、画像左上の再生ボタンを押せばデバッグが始まります。
ブレークポイントはエディタの行番号の少し左をクリックすると、画像のように赤い丸が表示されます。
image.png
画像のように設定したブレークポイントの位置でプログラムが止まれば成功です!
image.png
あとはウォッチ式に変数を追加したり、ステップ実行してみたり色々試してみてください。

まとめ

VSCodeを使ってDockerコンテナ上のRustアプリケーションのデバッグ方法をまとめました。
Dockerコンテナ上のデバッグは何かと面倒なイメージでしたが、VSCode Remote Developmentが登場したことにより、かなり簡単に設定できるようになり感動しました。

それでは良いRustライフを!

TomoProg

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