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

dockerを使ってローカルファイルを実行する

dockerを最近勉強し始めたのでその備忘録
初心者向け記事にファイルの扱いがあまり記載されてないのでメモ
間違いやもっと簡素なやり方がありましたらご指摘ください!

やりたいこと

dockerのcontainerを使い、ローカルにあるpythonスクリプトを実行したい

docker_test.py
if __name__ = "__main__":
  print("Success!")

準備

1:python imageをpullしてくる
$ docker pull image python
2:containerを作成する
$docker container run -it python
Python 3.9.2 (default, Feb 19 2021, 17:11:58) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

runする時に--nameオプションを付ければcontainerの名前を決めることができる(しない場合、適当な名前がつく)

その後、control+P+Qでdettachする。
control+dまたはexitでもいいが、その場合、コンテナがstopしてしまうので、restartする

$docker container restart charming_mirzakhani

charming_mirzakhaniの部分はcontainer id もしくはcontainer name
container nameやcontainer idはdocker ps -aで確認できる

$docker ps -a
CONTAINER ID   IMAGE     COMMAND        CREATED             STATUS                     PORTS     NAMES
6a55c7106a6c   python    "python3"      About an hour ago   Exited (0) 9 minutes ago             charming_mirzakhani
3:(ローカルにある)実行したいファイルをcontainer内のフォルダにコピーする
$docker cp /path/to/docker_test.py charming_mirzakhani:/usr/local/src/

docker cp [ローカルファイルへのパス] [container name or id]:[container内のパス]という構文
上の例ではcharming_mirzakhaniコンテナ内の/usr/local/src/ディレクトリにローカル(ホストOS)にあるdocker_test.pyをコピーしている

4−1:スクリプトを実行する
$docker container exec -w /usr/local/src/ charming_mirzakhani python docker_test.py
Success!

起動中のコンテナでの作業なので、execを使う
-wオプションはcdみたいなもので、実行時のカレントディレクトリを/usr/local/src/にする

4−2:bashを利用する方法

コンテナ内でbashに入ることもできる

$docker exec -it charming_mirzakhani bash
root@6a55c7106a6c:/#

この後はいつもローカルでやっているように実行できる

root@6a55c7106a6c:/# /usr/local/src/docker_test.py
Success!

バインド・マウントを使う方法

ローカル(ホストOS)側のディレクトリをcontainerにマウントする

$docker container run -v "$PWD":/usr/local/src -it python bash
root@7018bfa18828:/#

-vオプションでマウントを指定する、[ホスト側のpath]:[container側のpath]という構文
上の例では、ホスト側のカレントディレクトリをcontainerの/usr/local/srcにマウントしている

bashを使って確認してみると、

root@7018bfa18828:/# ls usr/local/src/
docker_test.py
root@7018bfa18828:/#

確かに、カレントディレクトリがマウントされている
なので後はこれを実行すれば良い

root@7018bfa18828:/# cd usr/local/src/
root@7018bfa18828:/usr/local/src# python docker_test.py 
Success!
root@7018bfa18828:/usr/local/src#

マウントを先にしておけば、bashに入らなくてもcontainer起動時は以下のようにも実行できる

$ docker container exec 7018bfa18828 python /usr/local/src/docker_test.py

参照

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

Dockerって知ってる?#1

はじめに

Dockerって知ってますか?
僕はあんまり知りません。

でも、会社のチーム内でみんなが使うコンテナ基盤を構築する事になりました。
いや、本当は一年前に引き受けて寝かしてました。すみません。

そこで、急遽Dockerを学び直すことにしました。

"学び直す" とか、いかにも知ったような口をたたきましたが、以前少し本を読んで動かして「こんな感じなんだなー」って掴んだ程度なので雑魚です。

知ったような口をたたけるようになる為の学習記録です。
要するに雑魚の雑記です。

宜しくお願いします。

日本語マニュアル

いきなり離脱提案です。
正直、この記事を読むよりマニュアル読んだ方がいいと思うので、日本語マニュアルのリンクも掲載しておきます。

環境

MacBook Pro
OS:Catalina
※コマンドの内容的にはWindowsでも大丈夫だと思います。

Dockerってなに?

Dockerは、ごっついエンプラサーバや手元のMacBookに、かんたんに任意の環境が作れるものです。

例えば、PHPのプログラマであれば、任意のバージョンのPHPがPCにインストールされていると思います。
そういった個々の環境をイメージという単位でまとめたものが、Docker社のDockerHubというところにまとめられています。

Dockerを使うユーザは、そのイメージをダウンロードして、コンテナという形で利用することが出来ます。

それはつまり、PCに直接PHPをインストールしなくても使える事を意味します。
RubyやPythonといった他の言語の実行環境はもちろん、ApacheやNginxといったWebサーバもかんたんに構築できます。

誤解を恐れず言うとコンテナの正体はディレクトリなので、作った環境を壊すこともかんたんです。

ダウンロードしたDockerイメージはカスタマイズして、またDockerHubにアップロードすることも出来ます。
なので、気になるあの子や、ムカつくあいつまで、誰でもかんたんにPC上に同じ環境を再現出来るということです。

再現性が高いという事は、検証環境で動いたアプリケーションが、本番環境で動かない!みたいなトラブルが起きにくいということですね。

Docker for Macのインストール

とりあえずインストールしましょう。
Windowsの人利用中にOSに合わせてDocker for WindowsかDocker ToolBoxなるものをインストールして下さい。

Docker for Macは、さきほども出てきたDockerHubというDocker社がホスティングするサーバからダウンロードします。

インストールウィザードはそのままパシパシ進めていけばOKです。

インストールが終わるとメニューバーの右側にクジラのマークが表示されます。
起動中はアニメーションがせかせか動いていますので、停止すれば使用可能です。

Dockerの設定

インストールしたらさっそくアプリを起動して、Dockerの設定画面を確認しましょう。
基本的にはいじるところほぼはないと思いますが、いくつかピックアップして説明します。

設定は歯車のマークをクリックします。

General

image.png

ホストOS(今回だとMacOS)起動時のDockerの自動起動や自動アップデートなどの設定が可能です。

Dockerは性質上、起動しているだけで、それなりにメモリを食います。
なので、使わないときはクジラをクリックQuitしておきましょう。

また、必要なときだけ立ち上げたい派の人は、下記のチェックを外しておきましょう。

Start Docker Desktop when you log in

他はとりあえずそのままでいいと思います。

Resources

image.png

ADVANCED

Dockerに割り当てるメモリやストレージ等のハードウェアリソースを制御出来ます。

FileSharing

バインドマウントという機能で使用します。

Dockerに対してPCのフォルダ共有を許可する為の設定です。
表示されているフォルダはデフォルトで共有が許可されています。

Proxies

PCが直接インターネットに接続していな場合に設定が必要な場所です。

Network

コンテナをNetwork接続するために必要な設定ですが、特にデフォルトのままで問題ありません。

とりあえずこれだけ押さえておけば十分だと思いますので、次に進みます。

さっそくDockerイメージを取得して起動してみよう!

とりあえず下記のコマンドを実行してみましょう!

docker run hello-world   

何が起きた!?ってくらい、長々英字が表示されたと思います。
ゾッとしますね。

この中の番号付きリストは今何が行われたのかを、記載してくれています。
文中に雑に解説を入れたので見てみて下さい。イメージIDとかタグとかは後述します。

例)
% docker run hello-world 
Unable to find image 'hello-world:latest' locally   ←ローカルにハローワールドってイメージなかったよ
latest: Pulling from library/hello-world   ←latestってタグが付いたイメージをpullするよ
b8dfde127a29: Pull complete    ←このイメージIDのやつをpullしたったぜ
Digest: sha256:89b〜   ←ハッシュ値だよ
Status: Downloaded newer image for hello-world:latest   ←hello-world:latestをダウンロードしているよ

Hello from Docker!   ←DockerからのHello!!
This message shows that your installation appears to be working correctly.   ←このメッセージは正常にインストール出来てる事を表してるよ!やったね!

To generate this message, Docker took the following steps:   ←こんな流れで実行されたんだよ!
 1. The Docker client contacted the Docker daemon.   ←DockerクライアントはDockerデーモンに接続したよ!
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.   ←DockerデーモンはDockerHubからハローワールドイメージを引っ張ってきたよ!
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.   ←Dockerデーモンはイメージから新しいコンテナを作成したよ!(略
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.   ←Dockerデーモンは君の端末に結果を出力したよ!

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

まずdocker run コマンドって?

docker run とは下記のコマンドをまとめて実行するのと同義です。

docker pull →DockerイメージをDockerHubから引っ張ってくるコマンド
docker create →Dockerイメージからコンテナを作るコマンド
docker start →作成したコンテナを起動するコマンド

docker run コマンドの結果を分析してコンテナ起動の流れを理解する

今回起動したコンテナは、上記の結果を出力するだけのものです。
この実行結果からイメージ取得〜コンテナが起動されるまでの流れを理解しましょう。

実行結果の雑コメントからわかるように、まずDockerはローカルに同じイメージがないかどうかチェックします。
それは同一イメージIDの有無から判断します。

また、Dockerイメージにはタグという機能があります。
タグはバージョン管理等の目的で使用され、指定しなければlatestというタグのものが選択されます。

ここで言うDockerクライアントとは、DockerのインストールされたPCを示します。
DockerクライアントであるPC上では、Dockerデーモンというプロセスが動作しています。

上述の通り、まずDockerデーモンはローカル上に同一のイメージがないかをチェックして、ある場合はそのイメージを使用しますが、ない場合はDockerHubからダウンロードします。

その後、Dockerデーモンはダウンロードしたイメージを実行し、クライアントの端末であるターミナルへ結果を出力します。

ローカル上にあるイメージを確認する

下記のコマンドを実行するとDockerクライアントであるPCになんのイメージがあるか確認出来ます。
先程出てきたイメージIDやタグの名称等はこちらでも確認が出来ます。

docker images
例)
% docker images
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
hello-world              latest    d1165f221234   14 hours ago    13.3kB

ローカル上にイメージがあるということは、DockerHubからダウンロードしないでもコンテナが起動出来るという事になります。
もう一度下記のコマンドを実行いただけると先程より早く実行結果が表示されることが確認いただけると思います。

docker run hello-world

イメージにを複製して任意のタグ付けをする

下記のように指定することで任意のイメージを複製し、任意のタグ付けをすることが出来ます。

docker tag ローカルにあるイメージ名 新しいイメージ名:タグ名

異なるイメージ名ですが、同じイメージIDが2つあり、タグ名が異なっているのが確認出来ますね。

例)
% docker tag hello-world new-hello-world:original
% docker images                                  
REPOSITORY        TAG        IMAGE ID       CREATED         SIZE
hello-world       latest     d1165f221234   14 hours ago    13.3kB
new-hello-world   original   d1165f221234   14 hours ago    13.3kB

イメージの詳細情報を表示する

コンテナ起動時に実行されるコマンドやイメージをOSの情報が表示されます。
興味があれば見てみて下さい。

docker inspect イメージ名(イメージIDでも可)
例)
% docker inspect hello-world

イメージの削除

不要になったイメージは下記のコマンドで削除してください。

docker rmi イメージ名

そのイメージを使用してつくられたコンテナがあると削除出来ません。
その場合は、コンテナを削除するか、-fを付けると強制削除が出来ます。

例)
docker rmi -f hello-world

おしまい

今回学んだ内容は以下の通りです。

  1. Dockerのインストール
  2. Dockerイメージの取得
  3. コンテナの作成と起動
  4. インストールされたDockerイメージ一覧の確認
  5. イメージの詳細情報の確認
  6. Dockerイメージの複製とタグ付け
  7. Dockerイメージの削除

また次回〜。

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

Scala で作った Web アプリを Dockerize して動かす(令和最新版)

既存の Java アプリをコンテナ化するにあたり「JVM でマイクロサービスといえば Scala と Akka-HTTP だよな~」という気持ちで Qiita を徘徊していたところ、Scalaで作ったWebアプリをDockerizeして動かすという素晴らしい記事を発見できました。

とはいえ、上記の 3 年前の記事ということで若干手直しが必要な部分もありましたので、改めて記事としてまとめておくことにしました。参考になれば幸いです。

環境

Scala + Akka-HTTP で Web アプリを作成する

最初に、build.sbt に依存ライブラリを追加します。元記事との違いとしては、 Akka シリーズのバージョンのほか、SLF4J 対応のロギングライブラリとして logback を追加しています。1

build.sbt
val AkkaVersion = "2.6.8"
val AkkaHttpVersion = "10.2.4"
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
  "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
  // ロギングライブラリがないと SLF4J が怒るので logback も入れておきます
  "ch.qos.logback" % "logback-classic" % "1.2.3"
)

続いてソース本文です。Akka HTTP 10.2.0 での仕様変更を反映しています。

main.scala
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import scala.concurrent.Await
import scala.concurrent.duration.Duration

object main {

  def main(args: Array[String]): Unit = {

    // typed ActorSystem が導入されましたが、旧 ActorSystem も利用可能です。
    implicit val system = ActorSystem(Behaviors.empty, "my-sample-app")

    // GET /indexでリクエストのURLパラメータとUserAgentを返却する
    val route =
      (get & pathPrefix("index") & extractUri & headerValueByName(
        "User-Agent"
      )) { (uri, ua) =>
        logRequestResult("/index", Logging.InfoLevel) {
          complete(s"param: ${uri.query().toMap}, user-agent: ${ua}}")
        }
      }

    val host = sys.props.get("http.host") getOrElse "0.0.0.0"
    val port = sys.props.get("http.port").fold(8080) { _.toInt }

    // akka.http.scaladsl.HttpExt.bindAndHandle が非推奨になりました
    val f = Http().newServerAt(host, port).bind(route)

    println(s"server at [$host:$port]")

    Await.ready(f, Duration.Inf)
  }
}

sbt run でコンパイル・実行し、 http://localhost:8080/index?<クエリ>=<値> にアクセスして以下のように表示されたら成功です。

$ curl -f "http://localhost:8080/index?query=string"
param: Map(query -> string), user-agent: curl/7.66.0}

sbt-native-packager で Docker イメージを作成する

project/plugins.sbtsbt-native-packager を追加します。同プラグインは msi | rpm | deb などのネイティブパッケージのほか、OpenJDK イメージ をベースとした Docker イメージも出力できるすぐれものです。

project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.0")

build.sbt に Docker ビルド用の設定を追加します。

build.sbt
// DockerPlugin は JavaAppPackaging に依存します
enablePlugins(JavaAppPackaging)
enablePlugins(DockerPlugin)

// 普段 Dockerfile で指定する内容を記載します
// MAINTAINER タグは非推奨になったので記載の必要はありません。
packageName in Docker := "sample-webapp" // イメージ名に反映されます
version in Docker := "2.0.0" // タグに反映されます
dockerBaseImage := "openjdk:latest" // 利用したい JDK/JRE イメージが指定できます
dockerExposedPorts := List(8080)

その他 DockerPlugin で利用可能な設定は公式マニュアルを参照してください。

sbt docker:publishLocal で Docker イメージがビルドできます。

$ sbt docker:publishLocal
(中略)
[success] All package validations passed
[info] Sending build context to Docker daemon  26.37MB
[info] Step 1/20 : FROM openjdk:latest as stage0
(中略)
[info] Built image sample-webapp with tags [2.0.0]
[success] Total time: 7 s, completed 2021/03/06 23:17:24

自動生成された Dockerfile は以下のようになっていました。

target/docker/stage/Dockerfile
FROM openjdk:latest as stage0
LABEL snp-multi-stage="intermediate"
LABEL snp-multi-stage-id="2b6164e8-16a3-449b-a436-30bc51408376"
WORKDIR /opt/docker
COPY 1/opt /1/opt
COPY 2/opt /2/opt
USER root
RUN ["chmod", "-R", "u=rX,g=rX", "/1/opt/docker"]
RUN ["chmod", "-R", "u=rX,g=rX", "/2/opt/docker"]
RUN ["chmod", "u+x,g+x", "/1/opt/docker/bin/akka-http-example"]

FROM openjdk:latest as mainstage
USER root
RUN id -u demiourgos728 1>/dev/null 2>&1 || (( getent group 0 1>/dev/null 2>&1 || ( type groupadd 1>/dev/null 2>&1 && groupadd -g 0 root || addgroup -g 0 -S root )) && ( type useradd 1>/dev/null 2>&1 && useradd --system --create-home --uid 1001 --gid 0 demiourgos728 || adduser -S -u 1001 -G root demiourgos728 ))
WORKDIR /opt/docker
COPY --from=stage0 --chown=demiourgos728:root /1/opt/docker /opt/docker
COPY --from=stage0 --chown=demiourgos728:root /2/opt/docker /opt/docker
EXPOSE 8080
USER 1001:0
ENTRYPOINT ["/opt/docker/bin/akka-http-example"]
CMD []

マルチステージビルドを活用していていい感じですね。

ソース全文はこちらの GitHub 上のレポジトリに置いておきましたので、ご参考まで。

参考リンク


  1. logback は設定ファイルを追加しない場合ロギング出力は直接コンソールに出力されるので、コンテナレディなアプリを作成するには最適です。 

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

Dockerfileの基本

【Dockerfileの基本】

かめさん いつもありがとうございます。
米国AI開発者がゼロから教えるDocker講座

実際の業務では、DockerfileからDocker imageを作る。
Dockerfileの理解を深めることで、Dockerを使いこなす!

Dockerfileとは。

Dockerfileというテキストファイル。
Dockerの環境を作るもの。
Docker imageの設計図。
INSTRUCTIONに引数を付けた形で書く。

Dockerfileを作成

$ cd ~/Documents
$ mkdir docker
$ cd docker
$ Code Dockerfile

DockerfileからDocker imageを作る。

$ cd ~/Documents/docker
$ docker build .
$ docker build -t new-ubuntu .

Docker imageからコンテナを立ち上げる。

$ docker run -it new-ubuntu
$ docker

Dockerfileの基本 : 3つのINSTRUCTION

FROM

Dockerfileのベースとなるimageを決める。
基本的にOSを指定する。

例) FROM ubuntu:latest

RUN

Dockerfileを好きなようにカスタマイズ出来る。
RUNごとにレイヤーが作られる。

例) RUN apt-get update && apt-get install -y \
   curl \
   cvs \
   nginx

\ を使うと全てを同じ行にみなしてくれるので見やすくなる。
cacheを使うと、dockerビルドをする時に既にdockerレイヤーがあった場合に、改めてビルドをすることがないので、時短になる。

 ※ Docker imageのレイヤー数は最小限にする。

CMD

コンテナのデフォルトのコマンドを指定する。
原則はDockerfileの最後に記述する。

例) CMD ["/bin/bash"]

 ※CMDはレイヤーを作らない。

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

RaspberryPiにDockerを構築する(Ubuntu編)

2020年に書いた記事の続編その2です。今回はラズパイ(Ubuntu)のdocker環境を構築します。以下の記述は2021年3月執筆時点の情報です。

この記事はDockerをラズパイで動かそうシリーズ(?)の一つです

大まかな手順

  1. gitクローン
  2. dockerインストール
  3. docker-composeインストール
  4. Docker構築(Netdata導入)

バージョン関連

  • Raspberry pi 4B 8GB RAM
ソフト バージョン
Ubuntu 20.04.2 LTS (64bit)
git 2.25.1
docker 20.10.5, build 55c4c88
docker-compose 1.28.5

Git

開発用途には十分なスペックがあるので、今後ラズパイ上で開発することを見据えてgithubにはssh接続します。何回かやってますがよく忘れるので忘備録的に書いておきます。なお以下の記事の写経です。

秘密鍵の生成

デフォルトでssh鍵が保存されている場所に移動。

$ cd ~/.ssh

ラズパイ上で秘密鍵を生成します。

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): XXX
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in XXX
Your public key has been saved in XXX.pub
The key fingerprint is:
HOGEHOGE
The key's randomart image is:

公開鍵(XXX.pub)の内容をメモしておきます

$ cat pi_git_8GB.pub 
ssh-rsa <<<文字列>> ubuntu@linux

Githubに公開鍵を登録

https://github.com/settings/keys にアクセスして右上の「New SSH key」をクリック。事前にgithubにサインアップしてください。

スクリーンショット 2021-03-06 18.09.03.png

catで表示した公開鍵を貼り付けます。

スクリーンショット 2021-03-06 18.26.44.png

configを書く

.ssh配下にconfigファイルを作成する

$ touch ~/.ssh/config
$ vi ~/.ssh/config
~/.ssh/config
host github github.com
  HostName github.com
  User [githabのユーザ名]
  IdentityFile ~/.ssh/[秘密鍵]

Git clone

自分のgithubレポジトリの右上に「Code」をクリックし、「SSH」を選択。「git@github.com:k-ken-t4g/XXX.git」と書かれている箇所をコピペします。

スクリーンショット 2021-03-06 18.44.50.png

適当なディレクトリに移動してクローンします。

$ git clone git@github.com:k-ken-t4g/XXX.git
Cloning into 'XXX'...
Enter passphrase for key '/home/user/.ssh/XXX': 
remote: Enumerating objects: 80, done.
remote: Counting objects: 100% (80/80), done.
remote: Compressing objects: 100% (50/50), done.
remote: Total 515 (delta 37), reused 61 (delta 27), pack-reused 435
Receiving objects: 100% (515/515), 8.61 MiB | 4.45 MiB/s, done.
Resolving deltas: 100% (241/241), done.

Dockerインストール

以下の公式ドキュメントに則って構築します。公式レポジトリを構築する方法だと最新版を定期的に入手したり、dockerをバージョン指定して入手できます。以下はほぼ下のリンクの和訳です。

公式レポジトリをセットアップした上でインストール

古いdockerをアンインストール

$ sudo apt-get remove docker docker-engine docker.io containerd runc

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

事前準備として以下のコマンドを実行

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg

Dockerの公式GPG鍵を入手

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

ラズパイで「stable」版を入手したい場合は以下のコマンドを実行。nightlytestを入手したい場合はstableの箇所を修正するか追記してください。

echo \
  "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Docker Engineのインストール

$ sudo apt-get update
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io

(参考)古いバージョンをインストールしたい場合

レポジトリで利用可能なバージョンの一覧を取得

$ apt-cache madison docker-ce
 docker-ce | 5:20.10.5~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable arm64 Packages
 docker-ce | 5:20.10.4~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable arm64 Packages
 docker-ce | 5:20.10.3~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable arm64 Packages

特定のバージョンをインストールする

sudo apt-get install docker-ce=<VERSION_STRING> docker-ce-cli=<VERSION_STRING> containerd.io

動作確認

sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
256ab8fe8778: Pull complete 
Digest: sha256:89b647c604b2a436fc3aa56ab1ec515c26b085ac0c15b0d105bc475be15738fb
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

マニュアルインストール

とりあえず下のコマンドを打っておけば最新版のdockerは手に入ります

curl -sSL https://get.docker.com | sh

実行結果は以下の通り

+ sudo -E sh -c docker version
Client: Docker Engine - Community
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:19:31 2021
 OS/Arch:           linux/arm64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:17:22 2021
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

取り除きたい場合は以下のコマンドを実行

$ sudo apt-get purge docker-ce docker-ce-cli containerd.io
$ sudo rm -rf /var/lib/docker
$ sudo rm -rf /var/lib/containerd

docker-composeのインストール

公式ドキュメントに沿ってdocker-composeをインストール

以下のコマンドを実行。docker-composeのバージョン1.28.5は2021年3月時点での最新安定版。

$ sudo apt install python3-pip #pip未インストールの場合
$ sudo pip3 install docker-compose

docker-composeの起動確認

$ sudo docker-compose --version
docker-compose version 1.28.5, build unknown

Netdata構築

Netdataでラズパイをモニターするためにdocker-composeで構築します。公式マニュアルが若干変更されたので、前回構築時点とは若干設定が異なります。また、ラズパイの温度が見られるように変更しています。

docker-compose.yml
version: '3'
services:
  netdata:
    #image: netdata/netdata
    build:
      context: ./
    ports:
      - 19999:19999
    cap_add:
      - SYS_PTRACE
    security_opt:
      - apparmor:unconfined
    volumes:
      - netdataconfig:/etc/netdata
      - netdatalib:/var/lib/netdata
      - netdatacache:/var/cache/netdata
      - /etc/passwd:/host/etc/passwd:ro
      - /etc/group:/host/etc/group:ro
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /etc/os-release:/host/etc/os-release:ro
    environment:
      - DOCKER_HOST=proxy:2375
    restart: unless-stopped
  proxy:
    image: tecnativa/docker-socket-proxy
    restart: always
    volumes:
     - /var/run/docker.sock:/var/run/docker.sock:ro
    privileged: true
    environment:
      - CONTAINERS=1

volumes:
  netdataconfig:
  netdatalib:
  netdatacache:
Dockerfile
FROM netdata/netdata

RUN sed -i -e 's/^# sensors=force/sensors=force/' /usr/lib/netdata/conf.d/charts.d.conf
COPY --chown=netdata:netdata rpi.html /usr/share/netdata/web
rpi.html
<!DOCTYPE html>
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en">

<head>
    <title>NetData Dashboard for RPi</title>
    <meta name="application-name" content="netdata">

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

    <meta property="og:locale" content="en_US" />
    <meta property="og:image"
        content="https://cloud.githubusercontent.com/assets/2662304/22945737/e98cd0c6-f2fd-11e6-96f1-5501934b0955.png" />
    <meta property="og:url" content="http://my-netdata.io/" />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="netdata" />
    <meta property="og:title" content="netdata - real-time performance monitoring, done right!" />
    <meta property="og:description"
        content="Stunning real-time dashboards, blazingly fast and extremely interactive. Zero configuration, zero dependencies, zero maintenance." />

</head>
<script>
    // this section has to appear before loading dashboard.js
    // Select a theme.
    // uncomment on of the two themes:
    // var netdataTheme = 'default'; // this is white
    var netdataTheme = 'slate'; // this is dark
// Set the default netdata server.
// on charts without a 'data-host', this one will be used.
// the default is the server that dashboard.js is downloaded from.
// var netdataServer = 'http://my.server:19999/';
</script>

<!--
    Load dashboard.js
    to host this HTML file on your web server,
    you have to load dashboard.js from the netdata server.
    So, pick one the two below
    If you pick the first, set the server name/IP.
    The second assumes you host this file on /usr/share/netdata/web
    and that you have chown it to be owned by netdata:netdata
-->
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
<script type="text/javascript" src="dashboard.js?v20190902-0"></script>

<script>
    // Set options for TV operation
    // This has to be done, after dashboard.js is loaded
    // destroy charts not shown (lowers memory on the browser)
    NETDATA.options.current.destroy_on_hide = true;
    // set this to false, to always show all dimensions
    NETDATA.options.current.eliminate_zero_dimensions = true;
    // lower the pressure on this browser
    NETDATA.options.current.concurrent_refreshes = false;
    // if the tv browser is too slow (a pi?)
    // set this to false
    NETDATA.options.current.parallel_refresher = true;
    // always update the charts, even if focus is lost
    NETDATA.options.current.stop_updates_when_focus_is_lost = false;
    // Since you may render charts from many servers and any of them may
    // become offline for some time, the charts will break.
    // This will reload the page every RELOAD_EVERY minutes
    var RELOAD_EVERY = 5;
    setTimeout(function () {
        location.reload();
    }, RELOAD_EVERY * 60 * 1000);
</script>

<body>

    <div style="width: 100%; text-align: center; display: inline-block;">
        <div style="width: 100%; text-align: center; display: inline-block;">
            <div style="width: 100%; height: 15px; text-align: center; display: inline-block;">
                <b>Raspberry Pi</b>
            </div>
        </div>

        <div style="width: 98%; height:100%; align: center; display: inline-block;">
            Overview
            <br />
            <div data-netdata="system.cpu" data-chart-library="gauge" data-title="CPU" data-units="%"
                data-gauge-max-value="100" data-width="20%" data-after="-420" data-points="420" data-colors="#22AA99">
            </div>
            <div data-netdata="sensors.temp_thermal_zone0_thermal_thermal_zone0" data-chart-library="gauge"
                data-title="Temperature" data-units="°C" data-gauge-min-value="20" data-gauge-max-value="100"
                data-width="20%" data-after="-420" data-points="420" data-colors="#884466">
            </div>
            <div class="netdata-container-easypiechart" style="margin-right: 10px; width: 11%; will-change: transform;"
                data-netdata="disk_space._" data-dimensions="avail" data-chart-library="easypiechart" data-title="Disk Space"
                data-width="11%" data-before="0" data-after="-300" data-points="300" >
            </div>
        </div>

        <div style="width: 100%; height: 19vh; text-align: center; display: inline-block;">
            <div style="width: 100%; height: 15px; text-align: center; display: inline-block;">
                <b>CPU</b>
            </div>
            <div style="width: 100%; height: calc(100% - 15px); text-align: center; display: inline-block;">
                <br />
                <div data-netdata="system.cpu" data-title="System CPU" data-chart-library="dygraph" data-width="49%"
                    data-height="100%" data-after="-300" data-dygraph-valuerange="[0, 100]" data-decimal-digits="0">
                </div>

                <div data-netdata="cpu.cpufreq" data-title="CPU Frequency" data-chart-library="dygraph" data-width="49%"
                    data-height="100%" data-after="-300" data-dygraph-valuerange="[500, 1800]" data-decimal-digits="0">
                </div>

                <div style="width: 100%; height: calc(100% - 15px); text-align: center; display: inline-block;">
                    <div data-netdata="system.load" data-title="System Load" data-chart-library="dygraph"
                        data-width="49%" data-height="100%" data-after="-300" data-dygraph-valuerange="[null, null]">
                    </div>
                    <div data-netdata="sensors.temp_thermal_zone0_thermal_thermal_zone0" data-title="CPU Temperature"
                        data-chart-library="dygraph" data-width="49%" data-height="100%"
                        data-dygraph-valuerange="[30, 90]" data-after="-300" data-decimal-digits="-1">
                    </div>

                </div>
            </div>
        </div>

        <div style="width: 100%; height: 19vh; text-align: center; display: inline-block;">
            <div style="width: 100%; height: 15px; text-align: center; display: inline-block;">
                <b>Memory</b>
            </div>
            <div style="width: 100%; height: calc(100% - 15px); text-align: center; display: inline-block;">
                <br />
                <div data-netdata="system.ram" data-title="System RAM" data-chart-library="dygraph" data-width="49%"
                    data-height="100%" data-after="-300" data-dygraph-valuerange="[null, null]">
                </div>

                <div data-netdata="system.swap" data-title="System SWAP" data-chart-library="dygraph" data-width="49%"
                    data-height="100%" data-after="-300" data-dygraph-valuerange="[null, null]">
                </div>
            </div>
        </div>

        <div style="width: 100%; height: 19vh; text-align: center; display: inline-block;">
            <div style="width: 100%; height: 15px; text-align: center; display: inline-block;">
                <b>I/Os</b>
            </div>
            <div style="width: 100%; height: calc(100% - 15px); text-align: center; display: inline-block;">
                <div data-netdata="system.io" data-title="System I/O" data-common-max="io" data-common-min="io"
                    data-chart-library="dygraph" data-width="49%" data-height="100%" data-after="-300"></div>
                <div data-netdata="system.net" data-title="Network Traffic" data-common-max="traffic"
                    data-common-min="traffic" data-chart-library="dygraph" data-width="49%" data-height="100%"
                    data-after="-300"> </div>
            </div>
        </div>
    </div>
    </div>
    <p align='center'>Netdata dashboard for Raspberry Pi. Powered by <a
            href='https://signal-flag-z.blogspot.com/'>Signal Flag Z</a></p>
</body>
</html>

docker-compose.ymlを配置したディレクトリに移動して以下を実行

$ sudo docker-compose up -d
Creating network "netdata_default" with the default driver
Creating volume "netdata_netdataconfig" with default driver
Creating volume "netdata_netdatalib" with default driver
Creating volume "netdata_netdatacache" with default driver
Building netdata
Sending build context to Docker daemon  241.7kB

Step 1/3 : FROM netdata/netdata
latest: Pulling from netdata/netdata
dce8679b510e: Pulling fs layer
d4daa44a897a: Pulling fs layer
dfd4cf4c2021: Pulling fs layer
9959791c8ab8: Pulling fs layer
435fcc90d0ca: Pulling fs layer
9e846689d9ed: Pulling fs layer
97cff47946d6: Pulling fs layer
9959791c8ab8: Waiting
435fcc90d0ca: Waiting
9e846689d9ed: Waiting
97cff47946d6: Waiting
dce8679b510e: Verifying Checksum
dfd4cf4c2021: Verifying Checksum
dfd4cf4c2021: Download complete
dce8679b510e: Pull complete
9959791c8ab8: Verifying Checksum
9959791c8ab8: Download complete
435fcc90d0ca: Verifying Checksum
435fcc90d0ca: Download complete
97cff47946d6: Verifying Checksum
97cff47946d6: Download complete
9e846689d9ed: Verifying Checksum
9e846689d9ed: Download complete
d4daa44a897a: Verifying Checksum
d4daa44a897a: Download complete
d4daa44a897a: Pull complete
dfd4cf4c2021: Pull complete
9959791c8ab8: Pull complete
435fcc90d0ca: Pull complete
9e846689d9ed: Pull complete
97cff47946d6: Pull complete
Digest: sha256:e42ef5a9aadc9e2d8680c43b220d9f4aae63044347d5362b372db8db60cc772e
Status: Downloaded newer image for netdata/netdata:latest
 ---> 2cefb6d78bdb
Step 2/3 : RUN sed -i -e 's/^# sensors=force/sensors=force/' /usr/lib/netdata/conf.d/charts.d.conf
 ---> Running in 3c95f71f51b0
Removing intermediate container 3c95f71f51b0
 ---> 0fe6b74eff24
Step 3/3 : COPY --chown=netdata:netdata rpi.html /usr/share/netdata/web
 ---> 334ecfb948fc
Successfully built 334ecfb948fc
Successfully tagged netdata_netdata:latest
WARNING: Image for service netdata was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Pulling proxy (tecnativa/docker-socket-proxy:)...
latest: Pulling from tecnativa/docker-socket-proxy
b538f80385f9: Pull complete
5216d9715640: Pull complete
18199a78a738: Pull complete
3e45a152932c: Pull complete
Digest: sha256:6c22b9545adc95258af9deffdde6c0ce0a0a70716771e5a4e02d24d1b6e0dda1
Status: Downloaded newer image for tecnativa/docker-socket-proxy:latest
Creating netdata_netdata_1 ... done
Creating netdata_proxy_1   ... done

http://[ラズパイのIPアドレス]:19999/にアクセスしてログが取得されているか確認。

スクリーンショット 2021-03-06 21.20.45.png

小ネタ:データ移動

PostgresのDocker Volumeに入っているデータを、そっくりそのまま違うマシンにコピーして動かせるか軽く調べてみた結果なさそうだったので、愚直に以下の方法でやりました。

後で調べた結果、もっと楽な方法があったのでリンクを貼ります。

  • Dockerコンテナに入る
$ sudo docker exec -it [コンテナ名] bash
  • PostgresのDockerコンテナ内でpg_dumpall。参考:pg_dump
$ pg_dumpall -f 20210307.sql -U postgres
sudo docker cp [コンテナ名]:[取り出したいファイルのパス] [ホスト側のパス]
  • 違うホストマシンにdumpしたファイルを転送(WinSCPCyberduckなど)。
  • ホストマシン側からコンテナへdump結果をコピー
sudo docker cp [ホスト側のパス] [コンテナ名]:[取り出したいファイルのパス]
  • リストア
psql -f 20210307.sql -U postgres

今後目指したいところ

以上でひとまず必要なDocker環境を準備しました。

今のところdockerを使ってデータ収集を行なっていますが、自宅にk8s分析環境を構築したいなと思っています。そこまで到達できるかは別にして。以下参考。

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

dockerコンテナ起動時にOnly one usage of each socket addressというエラー

前提と環境

私は素人です。
今回見舞われたエラーについて調べましたが、自分と同じ状況の人が見つからなかったので記事に残します。

Windows10homeでDocker for Windowsを使っています。
dockerで開発環境を作り、コンテナの最初の起動時にエラーが出ました。

つまづいたこと

$docker-compose up -dでコンテナを起動しようとしました。

$ docker-compose up -d
Creating network "learning-laravel-tdd_default" with the default driver
Creating learning-laravel-tdd_db-testing_1 ...
Creating learning-laravel-tdd_app_1        ...
Creating learning-laravel-tdd_db_1         ... error
Creating learning-laravel-tdd_db-testing_1 ... done
Creating learning-laravel-tdd_app_1        ... done
ocol/network address/port) is normally permitted.
Creating learning-laravel-tdd_web_1        ... done

ERROR: for db  Cannot start service db: Ports are not available: listen tcp 0.0.0.0:3306: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
ERROR: Encountered errors while bringing up the project.

するとエラーに。
どうやらポート番号3306に何か問題があってdbコンテナを起動できないみたいです。

とりあえずCannot start service db: Ports are not availableというエラー文を調べてみました。
似たようなエラーは多かったですが私の場合はOnly one usage of each socket addressとなっている箇所がaddress already in useの人が多く微妙に違うみたいです。

でもおそらくポート番号3306が他のプログラムと被っているのだろうと思いその線で試行錯誤することにしました。

やってみたこと

$docker-compose downで1度コンテナを停止。

$netstat -ano | findstr ":3306"を実行しポート番号3306でリッスンしているプロセスを調べました。

$ netstat -ano | findstr ":3306"
  TCP         0.0.0.0:3306           0.0.0.0:0              LISTENING       5464
  TCP         0.0.0.0:33060          0.0.0.0:0              LISTENING       5464
  TCP         [::]:3306              [::]:0                 LISTENING       5464
  TCP         [::]:33060             [::]:0                 LISTENING       5464

結果PIDが5464のプロセスが怪しいとわかりました。

そこでtasklistコマンドでPIDが5464のプロセスが何か調べました。

tasklist /FI "PID eq 5464"

イメージ名                     PID セッション名     セッション# メモリ使用量
========================= ======== ================ =========== ============
mysqld.exe                    5464 Services                   0     44,404 K

どうやらこのmysqld.exeが干渉しているみたいです。

mysqld.exeって何ですか?

調べました。
調べたんですが、
拡張子.exeはexecutable(実行可能)という意味らしい。
ってことしかよくわかりませんでした。

とりあえず止めてみた

タスクマネージャーから「タスクの終了」を選択。

もう1度$docker-compose up -dコマンドを実行。

$ docker-compose up -d
Creating network "learning-laravel-tdd_default" with the default driver
Creating learning-laravel-tdd_app_1        ... done
Creating learning-laravel-tdd_db-testing_1 ... 
Creating learning-laravel-tdd_db_1         ... done
Creating learning-laravel-tdd_web_1        ... done

、、、いけたな。

なんとも腑に落ちない

表面は問題がなくなったけど、mysqld.exeって何のアプリのために動いてたのか、止めちゃって良かったのか、、、

全然すっきりしない。

かと言ってこれ以上深追いする気力と体力がないので時間に解決してもらうことにします。

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

Dockerコンテナのデタッチとアタッチについて

はじめに

Dockerコンテナのデタッチとアタッチについての備忘録。

コンテナのデタッチとアタッチ

オプション:

オプション 省略形 説明 補足
--interactive -i Keep STDIN open even if not attached 標準入出力をコンテナに対して結びつける。つまり、入力した文字はコンテナに渡され、コンテナからの出力が画面に表示されるようになる。
--tty -t Allocate a pseudo-TTY pseudo tty (=pty: 疑似端末/仮想端末)を有効にする設定。(疑似端末は、カーソルキーやエスケープキー、[Ctrl]キーなどで操作するためのもの。)
--detach -d Run container in background and print container ID コンテナと端末を切り離してバックグラウンドで実行するオプション。

補足:

  • 「i」「t」「d」の3つのオプションの組み合わせて、-itdでつかうことが多し。
  • [Ctrl]+[P]、[Ctrl]+[Q]の順に押下で、コンテナからデタッチ。
  • $ docker attach CONTAINER ID でコンテナにアタッチ。
  • コンテナにアタッチ状態で[Ctrl]+[C]押下すると、コンテナをSTATUS: Exitedにできる。

実例:

デタッチドモードでコンテナを起動
$ docker run -itd nginx
9c68f8ccd978d7071ddbb915f38a552995c069c28f97a25a97681451d3896a68
アタッチモードでコンテナを起動
$ docker run -it nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

アタッチモードで起動したコンテナをデタッチする
[Ctrl]+[P]、[Ctrl]+[Q]を押下でデタッチし、コンテナが生きていることを確認。

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
51687903bc7c   nginx     "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   80/tcp    intelligent_lehmann
デタッチしたコンテナにアタッチする
$ docker attach 51687903bc7c
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナのアタッチとデタッチについて

はじめに

Dockerコンテナのアタッチとデタッチについての備忘録。

コンテナのアタッチとデタッチ

アタッチとは:

コンテナと接続している状態で、フォアグラウンドでコンテナが起動する。

デタッチとは:

コンテナと切り離した状態で、バックグラウンドでコンテナが起動する。

オプション:

オプション 省略形 説明 補足
--interactive -i Keep STDIN open even if not attached 標準入出力をコンテナに対して結びつける。つまり、入力した文字はコンテナに渡され、コンテナからの出力が画面に表示されるようになる。
--tty -t Allocate a pseudo-TTY pseudo tty (=pty: 疑似端末/仮想端末)を有効にする設定。(疑似端末は、カーソルキーやエスケープキー、[Ctrl]キーなどで操作するためのもの。)
--detach -d Run container in background and print container ID コンテナと端末を切り離してバックグラウンドで実行するオプション。

補足:

  • 「i」「t」「d」の3つのオプションの組み合わせて、-itdでつかうことが多し。
  • コンテナのアタッチとデタッチは切り替え可能。
    • [Ctrl]+[P]、[Ctrl]+[Q]の順に押下で、コンテナをデタッチできる。
    • $ docker attach CONTAINER ID でコンテナにアタッチできる。
  • コンテナにアタッチ状態で[Ctrl]+[C]押下すると、コンテナをSTATUS: Exitedになる。

実例:

アタッチモードでコンテナを起動
$ docker run -it nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

アタッチモードで起動したコンテナをデタッチする
[Ctrl]+[P]、[Ctrl]+[Q]を押下でデタッチし、コンテナが起動中であることを確認。

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
51687903bc7c   nginx     "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   80/tcp    intelligent_lehmann
デタッチしたコンテナに再びアタッチする
$ docker attach 51687903bc7c
デタッチドモードでコンテナを起動
$ docker run -itd nginx
9c68f8ccd978d7071ddbb915f38a552995c069c28f97a25a97681451d3896a68

備考:

コンテナ内に入っている状態で、[Ctrl]+[P]、[Ctrl]+[Q]を押下すると、ホスト側に操作が切り替わる。(コンテナは起動状態。)

$ docker run -it nginx bash

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS                      PORTS     NAMES
7fa53110f23f   nginx     "/docker-entrypoint.…"   22 seconds ago   Up 21 seconds               80/tcp    lucid_shirley
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Dusk+Dockerでブラウザテスト環境を作る

経緯

「カチカチカチカチカチカチカチカチカチ」
静寂な開発オフィスに響き渡るマウスクリック。それはキーボードのタイプ音よりも顕著である。なぜこんなにもクリック音が鳴り響くのか?それは動作チェックをしているためである。テストは正しく動作するか確認するとても重要な作業だ。
しかし、この作業に一体どれだけの時間を費やすことになるのだろうか。もちろん開発規模によるところであるが、エディタでコード記述→ブラウザで動作確認・・・(LOOP)
これは大きな時間を費やすことになるだろう。インターフェイスもキーボードからマウスに切り替えなければならない。煩雑な作業である。
IT化を提供する立場であるにもかかわらず、アナログな作業をしてしまうのはなんとも皮肉である。そして何より「これはエンジニアらしくない」と感じることとなった。
この問題をどうにかできないだろうか?LaravelにはDuskというブラウザテストを効率よく行うことができる機能がある。今回はその導入について挑戦したときのメモである。

環境

・Windows
・Laravel 7.30.4
・Docker(nginx1.15.6、php7.4.15、mysql5.7)

かねてよりLaravel開発用のDockerを使っているので、それに加える形での環境更新となる。既に上記のセットで動作している前提となる。mysqlは今回の更新作業に特に関係はしてこない。

今回の流れ

1. Dockerにseleniumコンテナを追加
2. Composerで「Laravel/Dusk」をインストール
3. LarvelでDusk関係のファイルを作成
4. 動作確認用に「VNC Viewer」をインストール
5. テストしてみる

1. Dockerにseleniumコンテナを追加

selenium・・・webブラウザでのテストを自動化するツール。DockerHubに公式のイメージがあるのでそれを使う。余談だが、pythonでもこれを介してブラウザ操作ができたりする。

docker-compose.yml
\\追加
 selenium:
    image: selenium/standalone-chrome-debug
    ports:
      - 4444:4444
      - 5900:5900
    depends_on:
      - web
    privileged: true

今回はデバッグ(テスト)用なので、下記のDebugging部分を参考にした。
ブラウザ操作をするにはdriverが必要だが、Laravelではデフォルトはchromeになっているので、そのままコンテナもchromeにする。

2. Composerで「Laravel/Dusk」をインストール

次はphpのコンテナでの作業。

docker exec -it phpのコンテナ名 bash

Laravelプロジェクト配下へ移動し、下記コマンドでLaravelDuskをインストールする。

composer require --dev laravel/dusk

ここで今回下記のエラーが出現。phpの拡張機能としてzipが有効になっていない旨の内容。
実は以前php7.4に更新したのだがzipが有効になっていなかったようである。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - laravel/dusk[v6.13.0, ..., 6.x-dev] require ext-zip * -> it is missing fro
m your system. Install or enable PHP's zip extension.
    - Root composer.json requires laravel/dusk ^6.13 -> satisfiable by laravel/d
usk[v6.13.0, 6.x-dev].

To enable extensions, verify that they are enabled in your .ini files:
    -
    - /usr/local/etc/php/conf.d/docker-php-ext-mysqli.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-pdo_pgsql.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
You can also run `php --ini` inside terminal to see which files are used by PHP
in CLI mode.

Installation failed, reverting ./composer.json and ./composer.lock to their orig
inal content.

下記コマンドでzip有無を確認するも有効でないようである。
一旦コンテナ内での作業は中止でDockerfileを修正する。

php --ri zip

Extension 'zip' not present.

phpコンテナ用のDockerfileも更新する。RUN apt-getの際、インストールするパッケージを追加した。
zlib1g-dev、libzip-dev、docker-php-ext-install zip
の3つを追加。

Dockerfile
RUN apt-get update \
&& apt-get install -y \
git \
zlib1g-dev \
libzip-dev \
zip \
unzip \
vim \
&& docker-php-ext-install zip 

変更後コンテナコンテナを再構築する。

docker-compose build

再度コンテナ内に入り、zipの再確認。無事有効になっている。

 php --ri zip

zip

Zip => enabled
Zip version => 1.15.6
Libzip headers version => 1.5.1
Libzip library version => 1.5.1

再度LaravelDuskをインストール。無事インストール完了。

composer require --dev laravel/dusk

3. LarvelでDusk関係のファイルを作成

引き続きphpコンテナでの作業。Laravelプロジェクト配下で書きのコマンドでDusk関係のファイルを作成する。

 php artisan dusk:install

無事完了するとtestsディレクトリにBrowserディレクトリとDuskTestCase.phpが作成される。

4. 動作確認用に「VNC Viewer」をインストール

せっかくなのでテスト実行中の様子を確認したい。したがってVNC Viewerを使ってコンテナ内で実行されるテストの様子を確認できるようにする。ちなみにテストの実行自体はViewerなくてもできる。
下記からインストール。

インストールしたらDockerで指定したlocalhost:5900で接続。passwordを要求されるが、公式通りデフォルトは「secret」でログインできる。
VNC Viewer 2021_03_06 18_12_17.png

ログインできて下記のような画面がでればOK。
localhost_5900 (111074b15212_99.0) - VNC Viewer 2021_03_06 14_54_58.png

5. テストしてみる

Dusk作成時にサンプルテストが出来上がっている。HomeにアクセスしたらLaravelのWelcomeが画面が表示されるかというシンプルなテスト。

ExampleTest.php
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    /**
     * A basic browser test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                    ->assertSee('Laravel');
        });
    }
}

下記のコマンドでテスト実行。

php artisan dusk

ところが下記のようなエラーになった。どうやらルートURLの設定がうまくできていないようである。
ちなみにエラーはtests/Browser/screenshotsにエラー時点のスクショができる。そして無事にテスト追加すると自動で削除される。

PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 1.56 seconds, Memory: 18.00 MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: net::ERR_CONNECTION_REFU
SED
  (Session info: chrome=88.0.4324.96)

/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Exception/WebDriverException.p
hp:139
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php
:371
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php:604
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/RemoteExecuteMethod.php
:27
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/WebDriverNavigation.php:41
/var/www/html/app/laravel/vendor/laravel/dusk/src/Browser.php:153
/var/www/html/app/laravel/tests/Browser/ExampleTest.php:19
/var/www/html/app/laravel/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:68
/var/www/html/app/laravel/tests/Browser/ExampleTest.php:21

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

failure-Tests_Browser_ExampleTest_testBasicExample-0.png

baseUrl()をオーバーライドするとことで対応。webはDockerのnginxコンテナの名前のことである。

DuskTestCase.php
<?php

namespace Tests;

use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;

abstract class DuskTestCase extends BaseTestCase
{
    use CreatesApplication;

    //追加
    protected function baseUrl()
    {
        return 'http://web';
    }

    /**
     * Prepare for Dusk test execution.
     *
     * @beforeClass
     * @return void
     */
    public static function prepare()
    {
        if (! static::runningInSail()) {
            static::startChromeDriver();
        }
    }

    /**
     * Create the RemoteWebDriver instance.
     *
     * @return \Facebook\WebDriver\Remote\RemoteWebDriver
     */
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments(collect([
            '--window-size=1920,1080',
        ])->unless($this->hasHeadlessDisabled(), function ($items) {
            return $items->merge([
                '--disable-gpu',
                '--headless',
            ]);
        })->all());

        return RemoteWebDriver::create(
            'http://selenium:4444/wd/hub', DesiredCapabilities::chrome()
        );
        // return RemoteWebDriver::create(
        //     $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
        //     DesiredCapabilities::chrome()->setCapability(
        //         ChromeOptions::CAPABILITY, $options
        //     )
        // );
    }

    /**
     * Determine whether the Dusk command has disabled headless mode.
     *
     * @return bool
     */
    protected function hasHeadlessDisabled()
    {
        return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
               isset($_ENV['DUSK_HEADLESS_DISABLED']);
    }
}

再度テスト実行。

 php artisan dusk

PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.09 seconds, Memory: 18.00 MB

OK (1 test, 1 assertion)

無事通過!これで一通りテスト環境が準備できた。公式リファレンスには様々なチェック項目があるのでテストが捗りそう。いろいろ試して行こうと思います。

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

NodeのDockerfileにopenapi-generatorを入れてaxios-typescriptを試したメモ

概要

前回、OpenAPIをRedocで出力するときに手元で試すようのDockerfileを作った。
openapi-generatorもそこに追加しようとしたらひと手間必要だったのでメモする。
手間をかけなくとも、公式がDockerのopenapi-generatorを配布しています。
ただの趣味。

ソースコード

Dockerfile

docker/node/Dockerfile
FROM node:15.10.0

# コンテナ上の作業ディレクトリ作成
WORKDIR /app

# 後で確認出来るようにpackage.jsonを作成
COPY .npmrc /app/.npmrc
RUN npm init -y
RUN npm i -D redoc redoc-cli swagger-cli
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"build\": \"swagger-cli bundle -r .\/openapi.yml -o .\/openapi.json\", /g" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"redoc\": \"npm run build \&\& redoc-cli bundle .\/openapi.json --options.menuToggle --options.pathInMiddlePanel  -o .\/dist\/index.html\", /g" /app/package.json
# openapi-generator-cliインストール。これだけだとjavaがなくて動かない。
RUN npm i -D @openapitools/openapi-generator-cli

# sdk download
RUN apt-get update
RUN apt-get install curl unzip zip -y
RUN curl -s "https://get.sdkman.io" | bash

# これを設定しないと、デフォルトの/bin/sh -c の動きとなる。そうすると、`source`コマンドが見つからない
SHELL ["/bin/bash", "-l", "-c"]

# sdk install
RUN source "/root/.sdkman/bin/sdkman-init.sh"
# java install
RUN sdk install java 8.0.282-open
# set openapi-generator-cli
RUN npx openapi-generator-cli version-manager set 5.0.1

RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"generate\": \"openapi-generator-cli generate -g typescript-axios -i .\/bundle.yml -o .\/dist\/client-axios \",/g" /app/package.json

package.json
{
  "scripts": {
    "generate": "swagger-cli bundle -r ./openapi.yml -o ./bundle.yml && openapi-generator-cli generate -g typescript-axios -i ./bundle.yml -o ./dist/client-axios ", 
    "redoc": "npm run build && redoc-cli bundle ./openapi.json --options.menuToggle --options.pathInMiddlePanel  -o ./dist/index.html",
    "build": "swagger-cli bundle -r ./openapi.yml -o ./openapi.json"
  },
  "devDependencies": {
    "@openapitools/openapi-generator-cli": "^2.1.26",
    "redoc": "^2.0.0-rc.48",
    "redoc-cli": "^0.10.2",
    "swagger-cli": "^4.0.4"
  }
}

確認

import { useEffect, useState } from 'react'
import CountersWithText from './organisms/CountersWithText'
import styles from './styles/Count.module.scss'
+ import { GasApi } from '~/lib/openapi/client-axios'
+ import axios from 'axios'


+ const api = new GasApi(undefined, undefined, axios.create())

const AccessCounter: React.FC = () => {
  const [accessNuber, setAccessNumber] = useState(0)
  const [accessStr, setAccessStr] = useState('LOADING')
  useEffect(() => {
    if (!apiUrl) return
    ;(async () => {
      try {
-        const response = await fetch(apiUrl, { method: 'POST' })
-        const json: { accessNumber: number } = await response.json()
-        setAccessNumber(json.accessNumber)
+        const response = await api.getAccessCounter()
+        setAccessNumber(response.data.accessNumber)
        setAccessStr('')
      } catch (_) {
        setAccessStr('ERROR')
      }
    })()
  }, [])

  return (
    <div className={styles.kotaAccessCounterWrapper}>
      {accessStr ? accessStr : <CountersWithText num={accessNuber} />}
    </div>
  )
}
export default AccessCounter

型が表示できるようになった。
image.png

参考

[備忘録]dockerでjava環境を一から作る
DockerfileのRUNやCMDをBashのログインシェルで実行させる

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

docker-composeを用いたローカルJenkins環境構築方法 メモ

  • マスター/スレーブ構成のJenkinsをローカル環境にdocker-composeで構築する手順をメモする。

Jenkins 構成

  • マスターノードとスレーブノードの2コンテナで構成する。

    • マスターノード : Jenkinsがインストールされたコンテナ
    • スレーブノード:マスターに操作されるコンテナ

    ※ビルド/実行環境などを分けたいときに利用する。

前提条件

  • docker/docker-composeはインストール済みであること。
  • 以下の構成のフォルダで作業する。
jenkins ____ jenkins_home        # マウント場所。docker-compose up実行時に生成される。
         L__ docker-compose.yml
         L__ Dockerfile_SLAVE     # スレーブ用Dockerfile      

作成手順

1. マスターコンテナ作成

  • docker-compose.ymlを用意する。※後でスレーブ用設定を追加する。
  version: "3"
  services:
    jenkins_master:
      container_name: jenkins_master
      image: jenkins/jenkins:latest
      ports:
        - 18080:8080
      volumes:
        - ./jenkins_home:/var/jenkins_home
  • マスターコンテナを起動する。

    • docker-compose.ymlを配置しているフォルダ内で以下のコマンドを実行する。
  docker-compose up -d
  • マスターコンテナに入る。
  docker exec -it <コンテナID> /bin/bash
  • 管理者パスワードを取得する。
  cat /var/jenkins_home/secrets/initialAdminPassword
  • SSH認証キーを作成する。
  ssh-keygen -t rsa -C ""
  • jenkins_home/.ssh/id_rsa(秘密鍵)、id_rsa.pub(公開鍵)が生成される。

    • マスターコンテナを停止する。
  exit
  docker-compose stop

2. スレーブコンテナ作成

  • スレーブコンテナ用Dockerfileを作成し、docker-compose.ymlと同一フォルダに配置する。
  FROM jenkinsci/ssh-slave
  RUN ln -s /usr/local/openjdk-8/bin/java /usr/local/bin/java

※Java のパスを通す。

  • docker-compose.ymlを修正する。

    • スレーブコンテナ用サービス設定を追加する。※以下は最終的なdocker-compose.yml
  version: "3"
  services:
    jenkins_master:
      container_name: jenkins_master
      image: jenkins/jenkins:latest
      ports:
        - 18080:8080
      volumes:
        - ./jenkins_home:/var/lib/jenkins_home
      links:
        - jenkins_slave01

    jenkins_slave01:
      container_name: jenkins_slave01
      build:
        context: .
        dockerfile: Dockerfile_SLAVE
      environment:
        - JENKINS_SLAVE_SSH_PUBKEY= #前述のid_rsa.pubの値を指定する。

3. 起動

  • コンテナ起動
  docker-compose up -d
  • 起動確認
  docker-compose ps
       Name                    Command               State                 Ports
  ---------------------------------------------------------------------------------------------
  jenkins_master    /sbin/tini -- /usr/local/b ...   Up      50000/tcp, 0.0.0.0:18080->8080/tcp
  jenkins_slave01   setup-sshd                       Up      22/tcp
  • URL:http://localhost:18080/にアクセスする。

  • 新規ノード追加などの設定を行う。

    ※「[SSH] No Known Hosts file was found at /var/lib/jenkins/.ssh/known_hosts. Please ensure one is created at this path and that Jenkins can read it. Key exchange was not finished, connection is closed.」のようなエラーが出て、上手くスレーブと接続できなかったが、ノード->スレーブ->起動方法の「Host Key Verification Strategy」を「Manually trusted key Verification Strategy」に変更することで接続できた。

参考情報

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

DockerコンテナでNode.jsを実行してみた

前の記事(コンテナ未経験なのでDockerを基礎から学んでみた)に引き続き、今回はコンテナでNode.jsのアプリケーションを動かし、ブラウザから開いてみることに挑戦してみました。

スクリーンショット 2021-03-06 13.44.31.png

実装は以下の手順で行いました。

  1. Node.jsアプリの作成
  2. Dockerfileの作成
  3. Dockerfileのビルド
  4. コンテナの起動
  5. ブラウザとアプリの接続

Node.jsアプリの作成

localhost:8080を開いたときにHi thereがブラウザに表示されることを目指します。

package.jsonindex.jsの中身は以下のようにします。

package.json
{
  "dependencies": {
    "express": "*"
  },
  "scripts": {
    "start": "node index.js"
  }
}
index.js
const express = require('express');

const app = express();

app.get('/', (req, res) => {
  res.send('Hi there');
});

app.listen(8080, () => {
  console.log('Listening on port 8080');
});

Dockerfileの作成

Dockerイメージをビルドしたとき、ファイルシステムにアプリに必要なプログラム(dependencies)をインストールし、メタ情報にアプリの起動コマンド(npm start)を導入することを目指します。

前の記事でredisをコンテナで立ち上げたときのように、試しにalpineのベースイメージを使ってDockerfileを作成してみます。

FROM alpine

RUN npm install

CMD ["npm", "start"]

これをビルドすると、RUN npm installの部分でこけます。
なぜかというと、alpineイメージは軽量であるがゆえに、nodenpmもプリインストールされていないからです。

そのため、ベースイメージにはnodenpmがプリインストールされているnodeイメージなどを選択する必要があります。ただ、nodeイメージにはnpmやnode以外にもテキストエディタツールなどの余計なプログラムもプリインストールされているので、軽量なalpineにnpmnodeがプリインストールされたnode:alpinenode:slimなどの最適化されたベースイメージを使うのがベストプラクティスです。

ベースイメージをnode:alpineに変えて再度ビルドしてみます。

FROM node:alpine

RUN npm install

CMD ["npm", "start"]

このビルドもこけます。
なぜなら、node:alpineのファイルシステムにpackage.jsonが含まれておらず、インストールするプログラムが不明だからです。もしたまたまハードドライブにpackage.jsonが含まれていたとしても、コンテナに割り当てられたハードドライブのセグメントにファイルがあるわけではないので、npm installは実行できません。

ここでDockerfile内に、RUNの前にファイルコピーするための記述COPYを加えます。
COPY ./ ./とすることで、ローカルのカレントディレクトリのファイル(package.json, index.js)をコンテナ内のファイルシステムにコピーすることができます。

FROM node:alpine

WORKDIR /usr/app

COPY ./ ./
RUN npm install

CMD ["npm", "start"]

*WORKDIR /usr/appという記述がありますが、これはファイルのコピー先(ワーキングディレクトリ)を指定しています。詳細は後述します。

これでようやくビルドが成功します。

(base) [9:42:15] → docker build -t suzuki0430/simpleweb:latest .                                                                               ~/Programs/docker/simpleweb
Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM node:alpine
 ---> b3dce3e0529f
Step 2/5 : WORKDIR /usr/app
 ---> Using cache
 ---> d995d6f0fd23
Step 3/5 : COPY ./ ./
 ---> 8b999fe7dc6e
Step 4/5 : RUN npm install
 ---> Running in 52b539c857e3

added 50 packages, and audited 50 packages in 3s

found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 7.0.8 -> 7.6.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v7.6.1>
npm notice Run `npm install -g npm@7.6.1` to update!
npm notice
Removing intermediate container 52b539c857e3
 ---> 5f7e4d45df44
Step 5/5 : CMD ["npm", "start"]
 ---> Running in e8085a051d01
Removing intermediate container e8085a051d01
 ---> e76e6a91dc2b
Successfully built e76e6a91dc2b
Successfully tagged suzuki0430/simpleweb:latest

コンテナの作成・起動を行います。

(base) [9:43:11] → docker run suzuki0430/simpleweb                                                                                             ~/Programs/docker/simpleweb

> start
> node index.js

Listening on port 8080

コンテナの起動も正常にできたみたいなのでこれで成功!といきたいところなのですが、ブラウザからアプリが開けません...えっ?
スクリーンショット 2021-03-06 10.26.21.png

ポートマッピング

アプリが開けなかったのは、コンテナのポート8080とローカルホストのポート8080が接続されていなかったためです。
ブラウザからlocalhost:8080にアクセスする際、localhost(ローカルPC)のポートに接続しようとします。ただ、今回はコンテナの中でNode.jsのアプリケーションを実行しているため、コンテナのポートと接続する必要があります。
スクリーンショット 2021-03-06 14.30.41.png

ローカルPCとコンテナのポートを紐付けることをポートマッピングと呼びます。
ポートマッピングを実行するためにはdocker run -p 8080:8080 [image id]コマンドを実行します。コンテナの作成・起動とポートマッピングを同時に実行してくれます。

これで開けるようになりました!
スクリーンショット 2021-03-06 10.43.55.png

キャッシュの利用

アプリは開けるようになったのですが、気になる部分が1つあります。それは今のDockerfileの内容だと、ファイルを更新してイメージをビルドする際にnpm installが必ず実行されてしまうことです。最終的にビルドは完了するので問題がないといったらないのですが、ビルドの待ち時間はできるだけ避けたいものです...

そこでキャッシュの利用を考えます。

DockerfileをRUN npm installの前にpackage.jsonのみをコピーするように書き換えます。

FROM node:alpine

WORKDIR /usr/app

COPY ./package.json ./
RUN npm install
COPY ./ ./

CMD ["npm", "start"]

すると、package.jsonの中身が同じであれば、RUN npm installまでの手順をキャッシュによってスキップすることができるようになります。

Step 3/6 : COPY ./package.json ./
 ---> Using cache
 ---> 1e7f34ded131
Step 4/6 : RUN npm install
 ---> Using cache
 ---> 8f0ae8a2f7f6

ワーキングディレクトリの指定

Dockerfile内にWORKDIR /usr/appという記述がありますが、ここではRUN, CMD, ENTRYPOINT, COPY, ADD, docker run, execで実行するコンテナプロセスのワーキングディレクトリを指定しています。

WORKDIR /と指定することももちろんできるのですが、ルートディレクトリにはいろんなファイルやフォルダが含まれているので、ファイル名が同じだったりすると干渉が起こってしまいます。そのため、コンテナプロセス用のディレクトリを指定してあげる必要があります。

コンテナ起動時にシェルを開くと、確かにワーキングディレクトリが/usr/appとなっています。

(base) [11:06:07] → docker run -it suzuki0430/simpleweb sh                                                                                     ~/Programs/docker/simpleweb
/usr/app #

おわりに

Node.jsをコンテナで動かすことができるようになりました。次はDocker-composeについて学習します。

参考資料

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

Laravelで一度は画像が表示されたのに、いろいろイジくってたら画像が表示されなくなった話

概要

初めてDockerを用いてLaravelで画像アップロードができる掲示板アプリを作った時のお話です。
アプリ制作中せっかく画像アップロード機能が正常に動いたのに、はじめに作ったディレクトリ構造が気に食わずちょいと直した結果、画像表示がされなくなってしまいました。その時の解決方法を備忘録として残します。

ディレクトリ構造

larapic
 ├── README.md
 ├── infra
 │   ├── mysql
 │   │   ├── data
 │   │   └── my.cnf
 │   ├── nginx
 │   │   ├── default.conf
 │   │   └── Dockerfile
 │   └── php
 │       ├── Dockerfile
 │       └── php.ini
 ├── docker-compose.yml
 └── laravel(以降laravel①)
     └── Laravel(以降laravel②)
         ├── app
         ├── bootstrap
         ├── config
         ├── ・・・

このディレクトリ構造で始めてしまいました。laravelが1ついらないですね。
ある程度アプリが出来上がり、余裕が生まれたところでこのディレクトリ構造を直したいと思い立ちました。
laravel②を①の階層に上げて、①を削除。これでOK〜!

…と思いきや、画像が表示されなくなってしまいました。ガーン。

解決方法

シンボリックリンクに原因がある模様です。一度シンボリックリンクを解除し、再度シンボリックリンクを貼ります。

terminal
# publicフォルダに移動しシンボリックリンクを解除
% cd public
% rm -rf storage
# 戻って再度シンボリックリンクを張る
% cd ../
% php artisan storage:link

これで画像が表示されました。

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

dockerメモ

dockerに関するメモ

dockerfileを更新してコンテナを更新する方法

docker build -t user_name/repository_name:tag .

user_name:あなたのユーザー名
repository_name:リポジトリ名(イメージ名)
tag:タグ

イメージの削除

コマンド: docker rmi [イメージID]

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

Docker(Mac)でgRPC FUSE有効時にMySQL8コンテナが起動しない問題について調査報告と解決策

はじめに

Docker環境でRailsアプリケーションを開発する際にDocker Desktop for MacのgRPC FUSEを有効にした状態でコンテナを起動すると、MySQL8のコンテナが起動しない問題が発生した。

こんな感じ

db_1     | 2021-03-05T17:42:39.341701Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
db_1     | 2021-03-05T17:42:40.003650Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
db_1     | 2021-03-05T17:42:40.011792Z 1 [ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('2') and data dictionary ('0').
db_1     | 2021-03-05T17:42:40.012090Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
db_1     | 2021-03-05T17:42:40.012378Z 0 [ERROR] [MY-010119] [Server] Aborting
db_1     | 2021-03-05T17:42:40.618413Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.23)  MySQL Community Server - GPL.

この問題はDocker 2.4.0から報告されている有名な問題らしく、gRPC FUSEをオフにすることで改善できる問題とのことだった。
参考:Mysql not starting in a docker container on MacOS after docker update

しかし、(本来ここについても調査すべきなのかもしれないがw)gRPC FUSEをオフにすることで、docker-composeを起動中にアプリケーションに編集を加えるとコンテナやアプリケーションがフリーズして再起動を必要とする…ということが起きてしまったため、
なんとかgRPC FUSEを有効にした状態でMySQL8コンテナを起動することはできないか、自分なりに調査をしてみました。




あくまでも「私はこのように調査したよ、このように対処したよ」という報告のような内容の記事ですので、ここに書いてある内容が正しいノウハウ、知識という認識で読まれるのはオススメしません。
この記事をきっかけに正しい知恵をいただいたり、もしくはこの問題についての議論が進むことを望みます。

実行環境

  • MacBook Pro 16inch 2019
  • macOS 11.2.2

  • Docker Desktop for Mac 3.1.0 ※gRPC FUSEを有効にする

docker-composeファイルの設定

docker-compose.yml
  db:
    image: mysql:8.0.23
    command: mysqld --default-authentication-plugin=mysql_native_password
    volumes:
      - ./mysql/mysql:/var/lib/mysql

いろいろわかったこと(原因は不明)

再ビルドしても起動しなかった

再度ビルドをしてみてクリーンな状態で起動してみてはどうか、と考えたが改善しなかった。
--no-cacheオプションも試しましたが関係ありませんでした。

別のログインユーザーは正常に起動した

私のMacBookにはプライベート用とそれ以外用とでユーザーアカウントをわけている。
今回、テストしてみたところ、同じRailsアプリケーション、同じdokker-composeファイルにもかかわらず、プライベート用のユーザーアカウントでは問題なくMySQL8コンテナを起動することができた。
インストールしているアプリケーションや開発環境の内容はほぼいっしょ(のはず)のため、もう一方のユーザーアカウントで起動しなかった理由はわからず…

インターン生のPCでも正常に起動した

私の勤務している会社で働いてくれているインターン生のMacBookでも正常に起動していた。
こちらのMacBookは最近使い始められたばかりで、特に設定やアプリのインストールがされているわけではないので、それが原因で起動した説…はあるかもしれない。。。

Dockerのバージョンを上げてみたが改善せず…

無駄でした…なお、バージョンを下げるのはなんだか気分が乗らなかったのでやめた。

docker-compose run db bashでコンテナに入ってみた => データベースが読み込まれていなかった

gRPC FUSEをオフの状態でMySQLコンテナに入り、MySQLを起動すると、
RailsのActiveRecordで作成したデータベーステーブルをshow databases;で見つけることができた。

しかし、gRPC FUSEを有効にしてから同様の処理をすると…そもそもMySQLにすらログインさせてもらえなかった。
つまり、データベースが読み込まれていない、ということだ。

なるほど…ということは、MySQLのデータを永続化させるために設定しているVolumesが怪しいのでは…?

MySQLコンテナのvolumesの記述を削除すると…起動した

MySQLデータを永続化させるためのvolumes設定を削除してみたところ、、、ついに、MySQLコンテナが正常に動作してくれた。
しかし、このままでは毎回コンテナ起動時にデータベースを作成しなければいけない。うーん、どうしたらいいものか。

そして見つけた、自分なりの改善策

MySQLのデータのマウント先を名前付きVolumesに設定 => 起動した!

Volumesが怪しいと思いいろいろ調べてみたところ、こちらの記事を見つけることができた。

Docker上のMySQLのデータをVolumeでホストのディレクトリにマウントすると権限周りで面倒なことになる
Dockerのvolumeでpermission deniedが発生した場合の解決法

Macでは発生しない問題(?)についての記事ではあったのですが、この記事を見て名前付きVolumesを設定したらどうなるだろう、と思った。

docker-composeファイルの設定のうち、volumesの設定を以下のように変更してみました。

docker-compose.yml
  db:
    image: mysql:8.0.23
    command: mysqld --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
  mysql_data:  # 名前付きVolumesを定義

すると…




動いた!!!(´;ω;`)




データもしっかり永続化できているので、docker-compose downしてから再度コンテナを起動させても、問題なくデータベースを読み込むことができた。
改めて思うと、永続化データをプロジェクトディレクトリ内にマウントする必要性も特に感じないので、名前付きVolumesに設定で全然問題ないんですよね。次からこのやり方で統一していこうと思います。




。。。しかし、今回MySQLコンテナをgRPC FUSE有効時にも正常動作させるのにかなり苦労したが、名前付きVolumesを使わずとも正常に動作する環境があるのも事実…なので完全な問題解決、理解には至っていないのだ。。。

おわりに

とりあえずの対処法は発見できたものの、まだこの問題の根本的な解消には至っていないと考えています。Dockerに対しての理解が浅いために、この程度までの調査、分析しかできませんでした。
今年に入ってこの問題に関する記事が新規で上がっていないところを見ると、皆さんすでに解決済み?なのかもしれませんが、もしかしたら今も困っている方がいらっしゃるかもしれません。

もし、この問題の原因、解決法に詳しい方がいらっしゃいましたらコメント欄でご教授いただけますと幸いです。
この記事が、DockerのgRPC FUSE問題に苦しむ方の解決のきっかけにつながることを願います。

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

パケット生成ツールあれこれ(ハンズオン記録)

この記事の目的

この記事は、とあるツール勉強会(ハンズオン)での記録とした記事である。
対象者は、初めてパケットを作成する人向けであり、ハンズオンの実施時間を1時間としている。
最終的に、紹介するツールを使って、お手製パケットを作成できるところまでを目的としている。
(パケット作成初心者を対象としており、高度なファジングテスト用途などはここでは紹介しません)

この記事の章構成

  • 【前編】Windows10(GUI環境)でのパケット作成ツールを利用する

    • 前編における前提条件
    • 必要となる知識
    • ツール類のインストール(ハンズオン開始までに各自で実施)
    • 注意事項
    • ipsendwinでのパケット送信
    • PacketBuilderでのパケット送信
  • 【後編】Linux環境(CLI環境)でのパケット作成ツールを利用する

    • 後編における前提条件
    • 必要となる知識
    • ツール類のインストール(ハンズオン開始までに各自で実施)
    • ハンズオン環境構築
    • netcatでのパケット送信
    • scapyを利用したパケット送信
    • dgramを使ったパケット送信

【前編】Windows10(GUI環境)でのパケット作成ツールを利用する

◆ 前提となる環境

ハンズオンの前提となる環境は下表のとおりです。

(Mac、Linux環境の方は前編は、後編までは手を休めていてください)

項目 条件
利用OS Windows10(Memoryは8GByte程度は欲しいところ)
パケット確認ツール Wireshark
ドライバ Win10Pcap
利用ツール1 ipsendwin
利用ツール2 PacketBuilder

◆ 必要となる知識

今回はIPv4のパケットを前提とするので、下記に占めるようなIPv4パケットの構造を知っておく必要があります。
(登場する単語が分からないときに、各自で参照できれば良いので、一度目を通しておいてください)

◆ ツール類のインストール(ハンズオン開始までに各自で実施)

私の環境では、以下の掲載の順番にインストールを行いました。

◆ 注意事項

  • ハンズオンでのパケット送信先は、各自が管理している端末のインタフェースとするようお願いします。
  • 作成したパケットを送信操作は、必要最小限にとどめるようにお願いします。 (必要以上に送信する行為は、DDoS攻撃に類する行為となりうる可能性もあることから、厳に慎むようお願いします。)

◆ ipsendwinでのパケット送信

1. 作成したいパケットの情報整理

  • パケットを作成する前に、送信元のアドレス情報と送信先のアドレス情報を事前に確認しておく
    • 送信元情報は、コマンドプロンプトで、ipconfig /allにて表示し、IPアドレスとMACアドレスを確認しておくこと。
    • 宛先情報は、Ping応答する端末(自分自身で管理しているサーバ等が良いです)
    • 操作端末が属するサブネットの外側であれば、先ほどのipconfigの結果からデフォルトゲートウェイのIPと、arp -aコマンドで対応するMACアドレスを確認しておく

2. 流れているパケットを観測する

  • Wiresharkを起動(Version3.4.3)
     ⇒メニューバー
      ⇒キャプチャ
       ⇒オプション(NICのIPアドレスが同じアドレスとなっているところ)
        ⇒選択したインタフェースのキャプチャフィルタ
          (半角小文字で、icmpと入力する。するとテキストボックスが緑になる)
          ⇒開始を押す

3. ipsendwinの利用

  • スタート→ipsendwinよりアプリを起動する。
     ⇒送信したいパケットの基本構成を定義
      ⇒ウインドウの中央付近にあるフレーム構成
       ⇒MAC + IP + ICMP + PAYLOAD を選択する

  • パケット構造の組み立て
     ⇒メニューバー
      ⇒設定
       ⇒フレーム定義
       (もしくはメニューバの直下のトンカチのアイコンをクリック)
        ⇒フレーム定義フォームが表示される
     ⇒フレーム定義フォーム
      ⇒MACタブ(先ほどの情報)
       ⇒IPタブ(─〃─)
        ⇒入力後にOKボタンを押して、フレーム定義フォームを閉じる。

  • パケットの送信
     ⇒ipsendwinのメイン画面
      ⇒インタフェース(送信するNICを選択していること)
       ⇒送信フレーム数(1とすること)
        ⇒送信間隔(1パケットのみを送るので、デフォルトの1000ミリ秒のままで良い)
         ⇒送信ボタン押下

  • パケットの確認
     ⇒Wiresharkのパケットリストを確認する。
      ⇒送信パケットが2重表示されていることがある。
       ※ Wiresharkが気を利かして、すべてのドライバの情報を拾っていると思われる
       ※ 自宅にて、送信端末外でキャプチャしたところ、実際には1パケットのみが送出されていることを確認している

4. 次に利用するPacketBuilderの下準備として、Wiresharkで取得したパケットをファイル保存する。

(停止操作)
 ⇒Wiresharkのメニューバー
  ⇒キャプチャ
   ⇒停止(またはメニューバー直下の赤■のアイコンを押下する)

(保存操作)
 ⇒Wiresharkのメニューバー
    ⇒メニューバーのファイル
     ⇒保存
      ⇒ファイルを保存するダイアログ
       ⇒任意の名前でパケットをファイル保存する。
        (拡張子はpcapngまたはpcapでよい。)
         ⇒次のPacketBuilder利用の際の入力ファイルとするので、保存場所を覚えておいてください。

◆ PacketBuilderでのパケット送信

1. 作成したいパケットの情報整理

⇒ ipsendwinと同様に進めることも可能ではあるが、ここでは、保存済みのパケットファイル(pcapファイル or pcapngファイル)を読み込み、読み込んだパケットデータをPacketBuilderで送信する手順を試してみる。
(ipsendwinの時と同じ環境でPingパケットを送信するだけなので、先ほど取得したパケットをそのまま再送信することが可能)

2. 流れているパケットを観測する(ipsendwinの時と全く同じ内容を掲載)

  • Wiresharkを起動(Version3.4.3)  ⇒メニューバー   ⇒キャプチャ    ⇒オプション(NICのIPアドレスが同じアドレスとなっているところ)     ⇒選択したインタフェースのキャプチャフィルタ       (半角小文字で、icmpと入力する。するとテキストボックスが緑になる)       ⇒開始を押す

3. PacketBuilderの利用

  • スタート⇒PacketBuilderより、アプリを起動する。
  • 先ほど保存したパケットファイルをインポートする。  ⇒メニューバー   ⇒ファイル    ⇒インポート     ⇒ファイルを開くダイアログ      ⇒先ほど保存したパケットファイルを選択して、ボタン(開く)を押下
  • パケットの送信
     ⇒PacketBuilderメイン画面の右側部分(パケットリストと表記している部分)に送信したいパケットが表示されていることを確認する。(表示されていないときは、パケットのインポートに失敗している可能性がある)
      ⇒送信したいパケットを一つ選択した状態でマウスを右クリックする
       ⇒代替メニューがポップアップされ、選択したパケットを送信というサブメニューをクリックする
        ⇒ダイアログ(選択したパケットを送信する)が表示されるので、オプションのアダプタに欄に、送信に用いるNICが指定されていることを確認する。(目的のNICが選択されていないときは、右側にあるボタン(選択)を押して、適切なNICを指定する)
         ⇒その他のオプションを変更することなく、ダイアログの下方のボタン(開始)を押下する。

  • パケットの確認(ipsendwinの時と全く同じ内容を掲載)
     ⇒Wiresharkのパケットリストを確認する。
      ⇒送信パケットが2重表示されていることがある。
       ※ Wiresharkが気を利かして、すべてのドライバの情報を拾っていると思われる
       ※ 自宅にて、送信端末外でキャプチャしたところ、実際には1パケットのみが送出されていることを確認している

◆ 補足

  • ipsendwinの利用時、パケットの確認で二重にパケットを観測する事象に遭遇しないために、Win10Pcapをインストールしないという方法があると思うかもしれません。 しかし、ipsendwinでは、最新のWinPcapだけだと正しく動作しないことがあるため、今回はWin10Pcapを個別にインストールしています。
  • ipsendwinで送信する際、Wiresharkでは2重にパケットが観測されてしまうのですが、Windows10の外側でパケットキャプチャを行ってみたところ、通過するパケットは1パケットだけが流れておりました。(質問されたときに備え、事前に確認をしておきました。)
  • 本事象について詳細を追求することはしませんが、機会があったら調べてみるつもりです。

ここからが今回のメインテーマです

【後編】Linux環境(CLI環境)でのパケット作成ツールを利用する

ここでの手順通り操作すれば、手順を通して必要な環境が出来上がり、目的のパケット送信までが完結できるようになっています。

◆ 前提となる環境

ハンズオンの前提となる環境を下記に示します。

  • Docker(コンテナ仮想化の基盤)をインストールしているMac、Windows、Linux。
  • Docker上に動作用コンテナ2台を作成し、2台がIPv4で相互通信できる環境とする。
  • インターネットへは、HTTP-Proxyを介さないで接続できる環境であることとする。

(補足)
※ 今回用意している動作用コンテナイメージは、Dockerfileを作りそびれてしまったなど諸事情から、マルチCPU対応ではないです。なので、Macの場合は多分IntelMacでないと動かない可能性がありますので、適合した環境を用意してください。
※ ここで用いるイメージには、主要なアプリがプリインストールされています。
※ CPUのアーキテクチャ的に対象外となっている方は、お手数ですが下方の構築手順(参考:独自で環境を作成したい方向け)を参考に各自でコンテナイメージ作成から実施していただくよう、お願いいたします。
※ HTTP-Proxy配下の環境の方は、Dockerとnodejsに対してHTTP-Proxyの設定を行うか、必要ファイルを個別でローカルにコピーし、ローカルインストールを行ってください。

◆ 必要となる知識

ハンズオンで必要となる知識は下記に掲載の通りですが、手順通りに操作するのであれば詳しくなくても問題ないです。

  • netcatの利用
    • Linuxの基本的なコマンド
  • scapyの利用
    • Pythonの基本的な使い方
  • dgramの利用
    • nodejsの基本的な使い方

◆ 動作用コンテナの作成

1. 本ハンズオンで利用したDockerのバージョン確認

docker --version
Docker version 19.03.8, build afacb8b7f0

2. 本ハンズオンで利用するコンテナイメージのダウンロード

docker pull jwat/ubuntu2004:latest

ダウンロードが完了したら、docker imagesコマンドにてイメージがダウンロードされたことを確認してください。

Server:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
jwat/ubuntu2004     latest              8239fcda55a7        1 hours ago         1.19GB

3. コンテナの起動

2台の動作用コンテナを区別するため、コンテナ1号機の名前をserverX01、コンテナ2号機の名前をserverX02、として進めます。

  • まずは1号機のコンテナ起動を行います。
    コンテナ名を、serverX01 とし、手元の端末からSSH接続(SCPでpcapファイルのダウンロード)可能な環境にしたいと思います。
    指定したコンテナにはOpensshserverがインストール済みであるので、下記のコマンドで起動します。
docker run -itd --privileged=true --name serverX01 -p 10022:22 jwat/ubuntu2004:latest /usr/bin/supervisord

上記で、--privileged=trueとしているのは、コンテナ内でroot権限によりアプリ操作を行うことを目的としています。
(tsharkによりパケットキャプチャをする際や、scapyでパケット送信する際に、root権限で実行が必要である)
また、-p 10022:22としているのは、コンテナ内でのOpen-SSHServerが使うポートである22番を、コンテナ外部からアクセスする際には10022番で待ち受けるための指定です。
各自の環境で都合の良い番号に変更することは可能ですが、この場合は接続時時に間違わないようにしてください。

最後の実行オプションである/usr/bin/supervisordは、OpenSSHServerをデーモンとして起動するために必要なオプションで、コンテナ内の設定ファイル/etc/supervisor/conf.d/startup.confsshdの起動コマンドを記述しています。
これらを指定することにより、コンテナ起動時にOpenSSHServerのプロセスが起動した状態を作り出しています。

  • 1号機の起動確認
docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
ee3bdc572302        jwat/ubuntu2004:latest   "/bin/bash"         4 seconds ago       Up 3 seconds                            serverX01
server@jw:~#
  • 1号機コンテナへの接続
docker exec -it serverX01 bash
  • 1号機コンテナのrootパスワード設定 rootユーザのパスワードは、各自で自由に設定しておいてください。
    ここで設定しておかないと、あとで手戻りとなります。
passwd root
  • 1号機コンテナのアドレス情報の確認 下記コマンドで、1号機のIPv4アドレスおよびMACアドレスを確認しておく。
    (デフォルトの起動方法だと、DHCPにてアドレスが払い出されるため)
root@ee3bdc572302:/# ip addr sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
82: eth0@if83: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@ee3bdc572302:/#

1号機のコンテナでの作業を終えたら、コンテナ内でexitコマンド実行により、コンテナから抜けて、元のOSへ戻ります。
その後に続いて、2号機も同様に実施します。

  • 2号機のコンテナ起動を行います。 コンテナ名を、serverX02 とし、手元の端末からSSH接続(SCPでpcapファイルのダウンロード)可能な環境にしたいと思います。 1号機とほぼ同様のコマンドですが、次の2点は間違えないようにしてください。
    • コンテナ名は、--name serverX02とすること
    • OpenSSHServerの待ち受けポート番号は、1号機やその他の使用済みポートと重ならないようにすること。(-p 20022:22など)
    docker run -itd --privileged=true --name serverX02 -p 20022:22 jwat/ubuntu2004:latest /usr/bin/supervisord
  • 2号機の起動確認 docker ps -aを実行すると、先ほど起動済みの1号機と合わせて2つのコンテナの状態を確認することができる。
docker ps -a
CONTAINER ID    IMAGE                    COMMAND         CREATED         STATUS        PORTS                   NAMES
ee3bdc572302    jwat/ubuntu2004:latest   "/bin/bash"     4 minutes ago   Up 3 minutes  0.0.0.0:10022->22/tcp   serverX01
abcf5389f3e7    jwat/ubuntu2004:latest   "/bin/bash"     3 seconds ago   Up 2 seconds  0.0.0.0:20022->22/tcp   serverX02
server@jw:~#
  • 2号機コンテナへの接続
docker exec -it serverX02 bash
  • 2号機コンテナのrootパスワード設定 rootユーザのパスワードは、各自で自由に設定しておいてください。 ここで設定しておかないと、あとで手戻りとなります。
passwd root
  • 2号機コンテナのアドレス情報の確認 1号機の時と同様に、2号機のIPv4アドレスおよびMACアドレスを確認しておく。
root@abcf5389f3e7:/# ip addr sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
84: eth0@if85: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
root@abcf5389f3e7:/#

2号機のコンテナでの作業を終えたら、コンテナ内でexitコマンド実行により、コンテナから抜けて、元のOSへ戻ります。

4. 2台のコンテナそれぞれへの接続

2台の動作用コンテナに対して、コンテナの外部にあるSSHクライアント(Teratermのようなターミナルアプリを使って)別々にSSH接続を行います。
そのことで、パケット送信側のコンテナと、パケット観測側のコンテナで、独立してコマンド実行を行えるようにする。
またSSH接続とすることで、パケット観測後に保存したPcapファイルをSCPコピーしたり、手元の端末から設定ファイルなどを送りやすくすることも目的としています。

※ Dockerが動作する端末では、コンテナの内部と外部をつなぐネットワークはNATネットワークとして起動している。
(今回のように、ネットワーク指定をしていない場合は、コンテナが所属するネットワークはDockerが用意するNATルータ配下となる)

※ 今回は、Dockerが稼働するOSのIPv4アドレスが192.168.0.101、Dockerが用意してくれているNATルータの内側のIPv4アドレスを172.17.0.1である前提で説明を行う。
(上記と異なる場合は、アドレスを読み替えて操作をしてください)
また、ここまでの手順により、1号機のコンテナのIPv4が172.17.0.2、2号機のコンテナのIPv4が172.17.0.3と仮定して、以降を進めていく。

この段階で、各コンテナへ接続する際は、下表のとおりとなる。
各自で操作のしやすい接続パターンを選択し、1号機、2号機、それぞれ別のターミナルにより接続すること。

接続元 接続先 SSH接続IPv4 SSH接続時ポート
Docker端末(172.17.0.1) 1号機(172.17.0.2) 172.17.0.2 22
Docker端末(172.17.0.1) 2号機(172.17.0.3) 172.17.0.3 22
Docker端末外 1号機(172.17.0.2) 172.17.0.1 10022
Docker端末外 2号機(172.17.0.3) 172.17.0.1 20022

以上で環境準備は完了です。

◆ netcatによるパケット送信(UDPパケットを簡単に送信するためだけの簡便な手法)

※ コンテナ環境にはあらかじめ下記のコマンドでインストールを行っている。

apt install -y netcat

1. パケット受信側の準備

tsharkを利用して、到着パケットを確認する。
2号機側で、port番号12345のパケットの観測を開始する。

tshark -f 'port 12345'

2. netcatによりUDPパケットの送信

1号機側で、2号機あてにパケットを送信する。

nc -u 172.17.0.3 12345

※ netcatは、臨時のプロキシサーバを稼働する際によく利用されるが、ポート指定だけの条件であれば、UDPパケットをサクッと送信する際にも利用できる。

netcatでのパケット送信例は以上で終了です。

◆ scapyによるパケット送信

※ コンテナ環境にはあらかじめ下記コマンドで、ディレクトリroot/にインストールを行っているので、手順通りに環境を作成した方は実施しなくてよいです。

git clone https://github.com/secdev/scapy.git

(個別で環境を用意する方は、各自でcloneしてださい)

1. パケット受信側の準備

tsharkを利用して、到着パケットを確認する。
2号機側で、icmpのパケットの観測を開始する。

tshark -Y icmp

2. 1号機側で2号機に向けて送信する。

scapyは/root/ディレクトリ配下にgit clone にてインストールされている。
今回は対話モードで利用するので、run_scapyを実行する。

cd /root/scapy/
./run_scapy

・send関数で直接送信する。
スラッシュでヘッダをつないでいく

send(IP(dst="172.17.0.3")/UDP(dport=12345))
send(IP(dst="172.17.0.3")/ICMP(code=0))

入力していないところは自動生成される

・変数にパケット構造を代入して、完成したら送信する。

p = IP(dst="172.17.0.3")/ICMP()

作成したら、途中で構造の確認を行う

p.show()

送信するときはsend関数で送信する。

send(p)

3. 送信パケットの定義を、pcapファイル内のパケットにより直接指定する

2号機側でパケット観測を継続していたら、Ctrl+Cなどでtsharkを停止する。
次に、tsharkをファイル書き込みモードで起動し、観測パケットをpcapファイルとして保存できるようにTsharkを起動する。
注意事項として、バックグラウンドではSSH通信が流れており、今回は観測不要なパケットであることから、TCP22番のパケットを除外指定することとする。

tshark -f 'not port 22' -w hoge.pcap

上記実行後に、ターミナルでパケット数がカウントアップされ始めたら、1号機にて適当なパケットを2号機あてに送信する。
2号機側で程よくパケットが蓄積されたことをカウンタで確認出来たら、Ctrl+cなどでtsharkでのパケット記録を停止する。
その後、1号機側でSCPコマンドにファイルをコピーし、2号機に保存されたpcapファイルを1号機側に複製する。

scp root@172.17.0.3:/root/hoge.pcap /root/scapy/

pcapファイルがコピーされたことを確認出来たら、下記のように変数pにpcapファイルのパスを代入する。

p=rdpcap("hoge.pcap")

読み込んだ"hoge.pcap"の概要を確認するには、

p.show()

を実行します。すると下記のように読み込まれたパケットの一覧がリスト表示されます。

0000 Ether / IP / ICMP 172.17.0.2 > 172.17.0.3 echo-request 0 / Raw
0001 Ether / IP / ICMP 172.17.0.3 > 172.17.0.2 echo-reply 0 / Raw
0002 Ether / IP / ICMP 172.17.0.2 > 172.17.0.3 echo-request 0 / Raw
0003 Ether / IP / ICMP 172.17.0.3 > 172.17.0.2 echo-reply 0 / Raw
0004 Ether / IP / ICMP 172.17.0.2 > 172.17.0.3 echo-request 0 / Raw
0005 Ether / IP / ICMP 172.17.0.3 > 172.17.0.2 echo-reply 0 / Raw

上記の一番左の数字は、Wiresharkなどでいうところのフレーム番号に相当します。

読み込んだパケットを送信したいときは、sendp関数を使います。
その書式に従いパケットを送信しようとすると sendp(p) となりますが、これだと遅延がほぼゼロのネットワークで一気に流してしまうだけでなく、宛先アドレスや送信元アドレスがネットワーク構成とは矛盾するパケットを送信してしまいます。その結果、観測地点では想定外の見え方となることが予想されます。
なのでフレーム番号0000のパケットだけを、フレーム番号指定で流すことにします。
その時のコマンドは下記です。(先ほど送信に利用したsend関数とは違い、sendp関数ですのでスペルミスに注意して下さい。)

sendp(p[0])

これでフレーム番号0番のパケットが送信されました。

では意地悪で、フレーム番号1番のパケットを送信してみることにします。
その書式は下記です。

sendp(p[1])

上記を実行すると、観測側ではARPのフレームが表示されるだけだと思います。
(なぜなら、MACアドレスとIPアドレスの送信と受信が反転した不正パケットを送信したことから、コンテナでパケットを送信する前にARPによるアドレス解決を実施し、悪いことにARP解決不能状態に陥ってしまうという事象が観測されるにとどまるためです)

scapyハンズオンはここで終了です。
(最後に、対話型のPythonを抜けるために、exit()を実行しておいてください。)

◆ dgram(nodejsモジュール)利用によるパケット送信

次に、dgramというnodejsのモジュールを使ってパケット送信する例を紹介します。
ここでは、数行のJavaScriptのコードを作成(サンプルのコピー&ペーストでよいです)を行うため、次の手順で進めます。

  • 1. 作業用ディレクトリの作成
  • 2. nodejsのプロジェクトの定義
  • 3. 利用モジュール(dgram)のインストール。(要ネット接続)
  • 4. パケット送信用のJavaScriptの作成
  • 5. パケット送信
  • 6. パケット受信側での観測結果確認

なお、ここで使用するコンテナについては、上記の1~5はコンテナ1号機(serverX01)、6はコンテナ2号機(serverX02を用いて行います。

1. 作業用ディレクトリの作成

作業する際はrootユーザで実施します。
まず始めに、適当なディレクトリに作業用ディレクトリを作成します。
ここではディレクトリ名をnodetestとして作成することにします。

mkdie nodetest

2. nodejsのプロジェクトの定義

先ほど作成した作業用ディレクトリに移動します。

cd nodetest

そして、作成プロジェクトを初期化します。

npm init

上記実行後、対話型形式で質問が出てきますので、すべてEnterキーを連打して、デフォルト値として回答します。(将来送信ロジックを組まれる方は、各自で適切に回答してください。)

3. 利用モジュール(dgram)のインストール。(要ネット接続)

次に、今回使用するnodejsのモジュールdgramをインストールします。

npm install dgram

4. パケット送信用のJavaScriptの作成

dgramを使ってパケットを送信するコードを作成します。
ここでは下記に示すサンプルコードをそのまま用いることにします。
(作成するファイル名は serverX00.jsとして進めていきます。)

// Module
var dgram          = require("dgram");

// Parameter
var packerGenerate = dgram.createSocket("udp4");
var strMessage     = "hogehoge"
var portDst        = 12345
var ipv4Dst        = "172.17.0.3"

// Packet Send
packerGenerate.send(strMessage, 0, strMessage.length, portDst, ipv4Dst)

// Fin.
console.log("Packet sent.")

5. パケット送信

いよいよパケットを送信します。
下記のようにコマンドを実行することで、パケットが送信されます。

node serverX00.js

送信側でPacket sent.と表示されたことを確認したら、Ctrl + cで処理を抜けてください。

6. パケット受信側での観測結果確認

ここで、コンテナ2号機(serverX02)のターミナルを見ると、下記のようにUDPパケットが観測されているはずです。

n x.xxxxxxxxx   172.17.0.2 ? 172.17.0.3   UDP 50 33561 ? 12345 Len=8
m y.yyyyyyyyy   172.17.0.3 ? 172.17.0.2   ICMP 78 Destination unreachable (Port unreachable)

※ 1. ここまでの作業の途中でtshark を停止ししてしまった方は、tsharkを起動し直して手順5のパケット送信をもう一度実施してください。
※ 2. それでも観測できない方は、これまでの手順の再点検をお願いします。

以上で、本日のパケット生成ツールあれこれハンズオンを終了とします。
各ツールごとに異なっている特徴を活かして、適材適所で使い分ける参考となりましたら幸いです。
(次回のネタの案としては、パケット受信、パケット解析、簡単なサーバ作成、、、etc.あります。要望がありましたらどれかをネタにして登壇してみるのも面白いかなと考えております。ありがとうございました。)

参考:独自で環境を作成したい方向け

(独自環境で参加される方は、ハンズオンまでに実施しておいてください。)

  • コンテナイメージのベースはUbuntu2004を使っています。
  • コンテナイメージ作成時の手順を以下に示します。(実行時はroot権限で実施しています)
  • 提供イメージではなくご自身で納得いく環境を整えたい方は、下記を参考にして各自でアレンジし、ハンズオンに望まれるようお願いします。

1. 作業用ディレクトリの作成(何でもよい)

2. ベースイメージのダウンロード

wget https://partner-images.canonical.com/core/focal/current/ubuntu-focal-core-cloudimg-amd64-root.tar.gz

3. Dockerfileの作成

FROM scratch
ADD ubuntu-focal-core-cloudimg-amd64-root.tar.gz /
# verify that the APT lists files do not exist
RUN [ -z "$(apt-get indextargets)" ]
# (see https://bugs.launchpad.net/cloud-images/+bug/1699913)

# a few minor docker-specific tweaks
# see https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap
RUN set -xe \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L40-L48
        && echo '#!/bin/sh' > /usr/sbin/policy-rc.d \
        && echo 'exit 101' >> /usr/sbin/policy-rc.d \
        && chmod +x /usr/sbin/policy-rc.d \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L54-L56
        && dpkg-divert --local --rename --add /sbin/initctl \
        && cp -a /usr/sbin/policy-rc.d /sbin/initctl \
        && sed -i 's/^exit.*/exit 0/' /sbin/initctl \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L71-L78
        && echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L85-L105
        && echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \
        && echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \
        && echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L109-L115
        && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L118-L130
        && echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes \
        \
# https://github.com/docker/docker/blob/9a9fc01af8fb5d98b8eec0740716226fadb3735c/contrib/mkimage/debootstrap#L134-L151
        && echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests

# make systemd-detect-virt return "docker"
# See: https://github.com/systemd/systemd/blob/aa0c34279ee40bce2f9681b496922dedbadfca19/src/basic/virt.c#L434
RUN mkdir -p /run/systemd && echo 'docker' > /run/systemd/container

CMD ["/bin/bash"]

4. Dockerイメージのビルド

docker build -t ubuntu2004simple:1 .

5. コンテナイメージ自身の内部環境構築

  • コンテナを起動する
docker run -itd --name test-01 ubuntu2004simple:1
  • コンテナへアクセスする
docker exec -it test-01 bash
  • 利用するアプリのインストール作業 (詳細は省略)
adduser ubuntu
gpasswd -a ubuntu sudo
apt update
apt upgrade
apt install -y sudo
apt install -y git
apt install -y vim
apt install -y curl
apt install -y wget
apt install -y jq
apt install -y openssh-server
apt install -y netcat
apt install -y tshark
apt install -y iproute2
apt install -y iputils-ping net-tools dnsutils
apt install -y netplan.io
apt install -y supervisor
mkdir /var/run/sshd
ln -s /usr/bin/python3.8 /usr/bin/python
apt install -y python3-pip
ln -s /usr/bin/pip3 //usr/bin/pip
git clone https://github.com/secdev/scapy.git
apt install -y nodejs npm
npm install n -g
n stable
apt purge -y nodejs npm
exec $SHELL -l
vi /etc/ssh/sshd_config
vi /etc/supervisor/supervisord.conf
  • コンテナ起動時にSSHを起動する設定 /etc/supervisor/conf.d/startup.confを下記のように作成する。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
  • Supervisordの管理画面を有効化する設定
; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                       ; sockef file mode (default 0700)

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.conf

[inet_http_server]
port=0.0.0.0:9001
username=ubuntu
password=************
  • rootユーザでSSH接続を許可する設定(/etc/ssh/sshd_config)

PermitRootLogin の設定を yesに変更する。

  • 内部環境構築後のスナップショットを作成する
docker commit test-01 testImage-01

参考資料

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

【入門】Terraformプロジェクトのセットアップ

AWSコンソールでぽちぽちではなく、コードでインフラを管理する方法を学びたいと思い、学習を始めました。
今回は実際に Terraform のプロジェクトの作成、公式のチュートリアルにあるEC2インスタンスの作成までをまとめます。

準備

  • 公式ページ
    • AWS、GCPなど、プロバイダーごとにチュートリアルが用意されてます。

  • Terraformの実行環境
    • homebrewなどで入れることが出来ます。
    • 公式の Docker コンテナがあるので、今回はこちらを使います。

プロジェクトの作成

いったんシンプルに試すため、下記構成で作ります。

work_dir/
 ├ .env
 ├ docker-compose.yml
 └ src/
   └ main.tf

ファイルはそれぞれ下記です。

// AWS credential info
AWS_ACCESS_KEY_ID =
AWS_SECRET_ACCESS_KEY =
docker-compose.yml
version: "3.8"
services:
  terraform:
    env_file:
      - .env
    image: hashicorp/terraform:light
    volumes:
      - ./src:/app/terraform
    working_dir: /app/terraform
src/main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }
  }
}

provider "aws" {
  profile = "default"
  region  = "ap-northeast-1"
}

resource "aws_instance" "example" {
  ami           = "ami-830c94e3"
  instance_type = "t3.micro"

  tags = {
    Name = "ExampleInstance"
  }
}

src/main.tf のリージョン、インスタンスタイプなどはお好みで。
今回は、ami-830c94e3をt3.microサイズのインスタンスで、東京リージョンで立ち上げるように書きました。

コマンドの実行

プロジェクトを作成、tfファイルを作成後に一度 init を実行します。

docker-compose run --rm terraform init
Creating network "mochimochi-terraform_default" with the default driver
Creating mochimochi-terraform_terraform_run ... done

Initializing the backend...

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

plan を実行することで、定義した内容を確認できます。

$ docker-compose run --rm terraform plan
Creating mochimochi-terraform_terraform_run ... done

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                          = "ami-830c94e3"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t3.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + secondary_private_ips        = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "ExampleInstance"
        }
      + tenancy                      = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

apply を実行することで、定義した内容が適用されます。

$ docker-compose run --rm terraform apply

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Creation complete after 13s [id=i-056f8b4b8de00beda]

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

completeと表示されましたね、実際にAWSコンソールを見に行って見ると、インスタンスが生成されていると思います。

apply実行後、インスタンスが生成されたことが確認できる

Terraformを使って、AMIから無事インスタンスを作ることができました。
他のAWSリソースの場合どうやるのか、また学んで投稿していこうと思います。

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