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

glibc, musl libc, go の resolver の違い

先日、resolv.conf で timeout を調整したいなと思うことがありました、しかし、Docker だの Kubernetes だのといった時代です。Linux しか使っていなかったとしても resolver が glibc のそれだとは限らないわけですね。そこで glibc, musl libc (alpine のやつです), go の resolver の違いを調べてみました。

環境

それぞれ docker container を使い、問い合わせの状況は tcpdump で確認しました。

macOS Catalina (10.15.7)
docker desktop 3.0.3 (51017)

container image は

alpine:3.12.3 / musl-1.1.24-r10
debian:buster-20201209 (10.7) / glibc 2.28-10

Go は mac 上で 1.15.6 を使い、クロスコンパイルして上記の alpine 上で実行しました。
クロスコンパイルなのでデフォルトで CGO_ENABLED=0 のはずですが、明示してビルドしました。

resolv.conf の options などの対応状況

resolv.confの例
nameserver 8.8.8.8
nameserver 8.8.4.4
search default.svc.cluster.local svc.cluster.local cluster.local asia-northeast1-b.c.project-id.internal c.project-id.internal google.internal
options ndots:5 timeout:5 attempts:2

resolv.conf の options にはいろいろありますが、今回の 3 つの resolver 全てで対応されているのは

  • ndots
  • timeout
  • attempts

の3つでした。

nameserver は勿論ですが search にも対応しています。
昔の musl libc は search (domain 補完) には対応していませんでしたが 1.1.13 から対応していました。
nameserver は複数回指定可能ですが、使われるのは3つまでです。4つ以上指定しても3つまでしか使われません。

それでは機能の違いを見ていきましょう

ndots

まずは ndots です、Kubernetes を使うようになってはじめて知りました。

指定するのは問い合わせるドメインに含まれる dot (.) の数です、Kubernetes のデフォルト設定では 5 (ndots:5) となっています。

例えば ping www.google.com とした場合、dot の数は 2 です。

dot の数が ndots 以上の場合

dot の数が ndots で指定した以上であればそれは FQDN であろうと判断し、search で指定したドメインを補完せずに DNS サーバーに問い合わせます。ここで、NXDOMAIN (そんなドメイン存在しないよ) が返ってきたら glibc と go は search で指定したドメインを補完して問い合わせます。しかし、musl libc は補完した問い合わせを行わないでそんなドメインは存在しないよということで終了してしまいます。

その昔、musl はそもそもドメイン補完に対応していなかったので当時から使っている人は常に FQDN で指定しているでしょうから問題ないでしょうが、知らないで ndots を小さくすると解決できるはずのドメインが解決できないかもしれません。

dot の数が ndots 未満の場合

dot の数が ndots 未満であった場合はまず search で指定した複数のドメインを順番に補完して問い合わせます、見つかるまで繰り返すため(これの上限を確認するの忘れた)、順序は重要ですかね。ただ、順序以上に ndots で指定する数が重要です、5 というのは結構多いです。Kubernetes でデフォルト設定だと service.namespace.svc.cluster.local という FQDN を指定したとしても 4 なわけです。search に並んでいる全てのドメインの補完を試した後に service.namespace.svc.cluster.local をそのまま問い合わせてやっと欲しい結果が得られるわけです、DNS サーバー側にネガティブキャッシュがあるとはいえ、DNS サーバーとの通信はその回数行われるわけですから、かなりの無駄です。GKE のデフォルト設定では6つもドメインが列挙されています。EKS は4つ。ndots を小さくして名前解決ができないドメインが発生してしまうよりも、無駄な問い合わせが増えたとしても名前解決できる値がデフォルトなっているわけですね。ndots はチューニングの余地がありそうです。

ちなみに、末尾に . をつけた場合はそれは FQDN だぞということを意味するので ping www.google.com. などとした場合はどの resolver でもドメインの補完は行いません。

nameserver

複数サーバーを指定した場合の扱いが glibc と musl で大きく異なるため、まず説明しておきます。
glibc と Go は一つ目のサーバーに問い合わせて、timeout 秒待っても応答がない場合に、次のサーバーに問い合わせます。
musl libc は複数の nameserver に同時にリクエストを送って最初に返ってきた応答を使います。3つのネームサーバーが指定されていたら3倍の問い合わせが発生してしまうわけですね。

timeout と attempts

timeoutattempts は関連しているため一緒に扱います。また、nameserver の数にもよるためそれごとにまとめます。

nameserver が1つの場合

glibc と Go は問い合わせを投げては timeout 秒待ち、応答がなければ再度問い合わせて、合計 attempts 回問い合わせます。

musl libc は timeout 秒を attempts で割った秒数をそれぞれの問い合わせで待ちます。つまり、リトライを含めた合計で最大 timeout 秒待つということです。 timeout:5 attempts:2 というデフォルト設定では 2.5 秒ずつ待ちます。

nameserver が2つの場合

glibc と Go は1つ目の nameserver に問い合わせて timeout 秒待ち、次は2つ目の nameserver に問い合わせます。これが attempts の1回分です。timeout:5 attempts:2 というデフォルト設定ではそれぞれの nameserver に対して2回ずつ、合計4回問い合わせます。その都度5秒待ちます。

musl libc は nameserver の項で書いた通り、複数の nameserver に対して同時に問い合わせるため、nameserver が1つの場合と同じです。

nameserver が3つの場合

nameserver が3つになると glibc の timeout の振る舞いが少し変わります。1つ目の nameserver に対しては timeout で指定した秒数待ちますが、2つ目は timeout で指定したのよりも短くなり、3つ目は timeout で指定したものより長くなります。
timeout:5 の場合は5秒、3秒、6秒となります。このセットを attempts 回繰り返します。

Go は常に timeout 秒待ちます。

musl libc はこれまでと同じです。

timeout 時のドメイン補完

何度問い合わせても timeout するような場合はもういろいろダメなのであまり重要ではないですが、動作に違いがあったので書いておきます。

timeout が続く状態で、2つ目以降の search ドメインを補完するのかどうか、glibc は2つ目以降のドメイン補完は行いませんが、補完なしの問い合わせを行います。Go は律儀に全てのドメイン補完を試しますし、補完なしでの問い合わせもします。musl libc は1つ目の補完の問い合わせは行いますが、それで終わりです。補完なしの問い合わせもありません。

Go の動作確認で使ったコード

package main

import (
    "context"
    "net"
    "os"
    "log"
)

func main() {
    ctx := context.Background()
    resolver := &net.Resolver{}
    for _, v := range os.Args[1:] {
        log.Printf("Resolving %s\n", v)
        names, err := resolver.LookupHost(ctx, v)
        if err != nil {
            log.Fatal(err)
        }
        for _, name := range names {
            log.Printf("%s\n", name)
        }
    }
}

ndots:5 の凶悪さを可視化する

文章でつらつら書いても分かりづらいので、 GKE 環境で storage.googleapis.com にアクセスしようとした場合にどんな問い合わせが発生するかを見てみましょう。default namespace に立てた Pod で ping storage.googleapis.com を実行した際の tcpdump の出力を加工しました。(横幅を抑えるために)

Cloud Storage の API にアクセスしようとする度にこの数の問い合わせが発生すると思うとゾッとしますね。マイクロサービスで Keep-Alive などをしていない場合は大量の内部通信でもこれが発生しているかもしれません。JVM などのように DNS のキャッシュをしてくれれば良いですけどね。

ndots:2 として常に FQDN で指定するということが徹底できると良いのかな。

14:53:34.706909 ▶︎ A? storage.googleapis.com.default.svc.cluster.local. (66)
14:53:34.707130 ▶︎ AAAA? storage.googleapis.com.default.svc.cluster.local. (66)
14:53:34.708881 ◁ NXDomain 0/1/0 (159)
14:53:34.708940 ◁ NXDomain 0/1/0 (159)
14:53:34.709051 ▶︎ A? storage.googleapis.com.svc.cluster.local. (58)
14:53:34.709133 ▶︎ AAAA? storage.googleapis.com.svc.cluster.local. (58)
14:53:34.709615 ◁ NXDomain 0/1/0 (151)
14:53:34.709689 ◁ NXDomain 0/1/0 (151)
14:53:34.709771 ▶︎ A? storage.googleapis.com.cluster.local. (54)
14:53:34.709816 ▶︎ AAAA? storage.googleapis.com.cluster.local. (54)
14:53:34.712211 ◁ NXDomain 0/1/0 (147)
14:53:34.712280 ◁ NXDomain 0/1/0 (147)
14:53:34.712387 ▶︎ A? storage.googleapis.com.asia-northeast1-b.c.my-project-id.internal. (83)
14:53:34.712479 ▶︎ AAAA? storage.googleapis.com.asia-northeast1-b.c.my-project-id.internal. (83)
14:53:34.716561 ◁ NXDomain 0/1/0 (189)
14:53:34.716623 ◁ NXDomain 0/1/0 (189)
14:53:34.716718 ▶︎ A? storage.googleapis.com.c.my-project-id.internal. (65)
14:53:34.716760 ▶︎ AAAA? storage.googleapis.com.c.my-project-id.internal. (65)
14:53:34.719891 ◁ NXDomain 0/1/0 (162)
14:53:34.720191 ◁ NXDomain 0/1/0 (162)
14:53:34.720304 ▶︎ A? storage.googleapis.com.google.internal. (56)
14:53:34.720390 ▶︎ AAAA? storage.googleapis.com.google.internal. (56)
14:53:34.724145 ◁ NXDomain 0/1/0 (145)
14:53:34.724352 ◁ NXDomain 0/1/0 (145)
14:53:34.724458 ▶︎ A? storage.googleapis.com. (40)
14:53:34.724500 ▶︎ AAAA? storage.googleapis.com. (40)
14:53:34.726930 ◁ 4/0/0 AAAA 2404:6800:4004:813::2010, AAAA 2404:6800:4004:81c::2010, AAAA 2404:6800:4004:81d::2010, AAAA 2404:6800:4004:81e::2010 (152)
14:53:34.726957 ◁ 16/0/0 A 172.217.161.80, A 172.217.175.16, A 172.217.175.48, A 172.217.175.80, A 172.217.175.112, A 216.58.197.144, A 172.217.25.208, A 172.217.25.240, A 172.217.26.48, A 172.217.31.176, A 172.217.161.48, A 172.217.174.112, A 172.217.175.240, A 216.58.220.112, A 216.58.197.208, A 216.58.197.240 (296)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang製PHPアプリケーションサーバRoadRunnerでLaravel-adminを動かす

アプリケーションサーバーの新潮流

昨今のPHP界隈では、Swooleに代表されるように、よくあるApache+mod_phpやPHP-FPMによる従来の構成と異なる実行方法を持つPHPのためのHTTPサーバまたはアプリケーションサーバが少しずつ注目を集めるようになっています。

RoadRunner

RoadRunnerもそんな新興のアプリケーションサーバの1つで、Go言語で書かれています。 HTTPのリクエストを前段のGoのHTTPハンドラがさばき、ロードバランサ/プロセスマネージャがPHPのWorkerにリクエストを割り振るという構成になっています。

通常のHTTPサーバだけでなく、RoadRunnerによるgRPCサーバの実装も公開されており、PHPの新しい可能性を見せてくれています。

RoadRunnerを試す

RoadRunnerはLaravelもサーポートしており、Laravel用のパッケージも開発されているので

今回はこれをベースにRoadRunner実装していきたいと思います。

RoadRunnerのインストール

まずは下記コマンドでパッケージのインストールを行います。

# roadrunner
composer require spiral/roadrunner "^1.8"
# roadrunner-laravel
composer require spiral/roadrunner-laravel "^3.4"

次に、下記コマンドを実行してパッケージ構成ファイル(./config/roadrunner.php)を作ります。

php artisan vendor:publish --provider='Spiral\RoadRunnerLaravel\ServiceProvider' --tag=config

.rr.ymlの作成

roadrunnerの設定ymlファイルを書きます。

80番ポートで起動するように設定します。

env:
  APP_REFRESH: true
http:
  address: 0.0.0.0:80
  http2:
    enabled: true
    h2c: true
    maxConcurrentStreams: 128
  workers:
    command: 'php ./vendor/bin/rr-worker'
static:
  dir: public
reload:
  interval: 1s
  patterns: [ '.php' ]
  services:
    http:
      dirs: [ '' ]
      recursive: true

諸々の微修正

logging.php

'stdout' => [
    'driver' => 'monolog',
    'handler' => StreamHandler::class,
    'with' => [
        //php://stdoutにするとエラーになってコケるのでphp://stderrに変える
        'stream' => 'php://stderr',
     ],
     'level' => 'debug',
],

composer.jsonにscriptを追加する。

"scripts": {
        "roadrunner:dev": [
            "composer dump-autoload",
            "./rr serve -v -d"
        ],
        "roadrunner:prod": [
            "./rr serve"
        ],
        "roadrunner:setup": [
            "rr get-binary"
        ],
    }

startup.shに起動コマンドを乗せる

# roadrunner
composer roadrunner:setup
chmod +x rr
composer roadrunner:dev

これで大体の設定は完了しました。

起動してみる

以下の手順で起動させます。

cp docker-compose.example.yml docker-compose.yml

docker-compose up -d

無事に起動できました。

スクリーンショット 2020-11-27 12.59.30.png (168.6 kB)

まとめ

今回はGolangで作られたPHPのweb・アプリケーションサーバーを使った。サンプルを作ってみました。
おもしろいですが、不安定なのでプロダクション環境ではあまり使うのはおすすめできないですね。

参考文献

https://nextat.co.jp/staff/archives/235

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

Golang製PHPアプリケーションサーバRoadRunnerを試す

アプリケーションサーバーの新潮流

昨今のPHP界隈では、Swooleに代表されるように、よくあるApache+mod_phpやPHP-FPMによる従来の構成と異なる実行方法を持つPHPのためのHTTPサーバまたはアプリケーションサーバが少しずつ注目を集めるようになっています。

RoadRunner

RoadRunnerもそんな新興のアプリケーションサーバの1つで、Go言語で書かれています。 HTTPのリクエストを前段のGoのHTTPハンドラがさばき、ロードバランサ/プロセスマネージャがPHPのWorkerにリクエストを割り振るという構成になっています。

通常のHTTPサーバだけでなく、RoadRunnerによるgRPCサーバの実装も公開されており、PHPの新しい可能性を見せてくれています。

RoadRunnerを試す

RoadRunnerはLaravelもサーポートしており、Laravel用のパッケージも開発されているので

今回はこれをベースにRoadRunner実装していきたいと思います。

RoadRunnerのインストール

まずは下記コマンドでパッケージのインストールを行います。

# roadrunner
composer require spiral/roadrunner "^1.8"
# roadrunner-laravel
composer require spiral/roadrunner-laravel "^3.4"

次に、下記コマンドを実行してパッケージ構成ファイル(./config/roadrunner.php)を作ります。

php artisan vendor:publish --provider='Spiral\RoadRunnerLaravel\ServiceProvider' --tag=config

.rr.ymlの作成

roadrunnerの設定ymlファイルを書きます。

80番ポートで起動するように設定します。

env:
  APP_REFRESH: true
http:
  address: 0.0.0.0:80
  http2:
    enabled: true
    h2c: true
    maxConcurrentStreams: 128
  workers:
    command: 'php ./vendor/bin/rr-worker'
static:
  dir: public
reload:
  interval: 1s
  patterns: [ '.php' ]
  services:
    http:
      dirs: [ '' ]
      recursive: true

諸々の微修正

logging.php

'stdout' => [
    'driver' => 'monolog',
    'handler' => StreamHandler::class,
    'with' => [
        //php://stdoutにするとエラーになってコケるのでphp://stderrに変える
        'stream' => 'php://stderr',
     ],
     'level' => 'debug',
],

composer.jsonにscriptを追加する。

"scripts": {
        "roadrunner:dev": [
            "composer dump-autoload",
            "./rr serve -v -d"
        ],
        "roadrunner:prod": [
            "./rr serve"
        ],
        "roadrunner:setup": [
            "rr get-binary"
        ],
    }

startup.shに起動コマンドを乗せる

# roadrunner
composer roadrunner:setup
chmod +x rr
composer roadrunner:dev

これで大体の設定は完了しました。

起動してみる

以下の手順で起動させます。

cp docker-compose.example.yml docker-compose.yml

docker-compose up -d

無事に起動できました。

スクリーンショット 2020-11-27 12.59.30.png (168.6 kB)

まとめ

今回はGolangで作られたPHPのweb・アプリケーションサーバーを使った。サンプルを作ってみました。
おもしろいですが、不安定なのでプロダクション環境ではあまり使うのはおすすめできないですね。

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

docker-composeで立ち上げたコンテナに外から通信する

背景

docker-composeで立ち上げたDBコンテナに他のコンテナからconnectionを貼りたかった
その手順の備忘録

手順

以下のコマンドでdocker-composeで立ち上げたnetworkIDとNameを調べる

$ docker network ls

docker runコマンドで先程取得したID or Nameを--netで指定して実行

$ docker run -it -v "$PWD":/path/to/dir --net <network ID or Name> <image名> <command>

これで指定したネットワーク先のホストやエイリアスで接続できる

ホスト名がない場合は--linkでエイリアスを作成する

$ docker run -it -v "$PWD":/path/to/dir --net <network ID or Name> --link <container Name>:<host Name> <image名> <command>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker x Laravel 脅威の1ファイル!最小構成でローカル開発環境を構築する

docker-compose.yml の1ファイル、ウェブサーバーコンテナ、データベースサーバーコンテナなしで作成する最小構成のDocker Laravel開発環境です。

環境

  • PHP: 7.4
  • Composer: 2.0
  • Docker: 20.10.2
  • Docker Compose: 1.27.4

docker-compose.yml 1ファイルのみ

docker-compose.yml

docker-compose.yml
version: "3.8"
services:
  composer:
    image: composer:2.0
    volumes:
      - ./backend:/work/backend
    working_dir: /work/backend

  app:
    image: php:7.4-cli-buster
    ports:
      - 8000:8000
    volumes:
      - ./backend:/work/backend
    working_dir: /work/backend
    command: php artisan serve --host=0.0.0.0

使い方

Laravelプロジェクトの作成

$ docker-compose run composer create-project --prefer-dist laravel/laravel .

# Laravelバージョン指定
$ docker-compose run composer create-project --prefer-dist "laravel/laravel=6.*" .

Laravelビルトインサーバーの起動

$ docker-compose up -d app

http://localhost:8000

こだわったポイント

Dockerfileのビルドが必要ないこと。
もはやカップ麺を作るより速く、お湯を沸かす時間だけで作れてしまうほどシンプルな構成です。

作った理由

Dockerfileのビルドの時間がかかるから。
Laravelのお試し環境を作るのに最強の構成を作るまででもないなという時。

GitHub

オススメの記事

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

docker環境+vscodeでrubocopを有効にする方法

動作環境

  • ruby 2.6.6
  • rails 6.1.0
  • vscode
  • docker 20.10.2

この記事が役に立つ人

  • docker環境上で既にrubocopを導入しており、コマンドライン上ではrubocopのコードチェック機能を使えるようになっていること

  • 上記の条件を満たした上でvscodeでrubocopのコード自動チェック機能を使いたい人

問題

vscodeにruby-rubocopをインストールしたのだが、コードの自動チェック機能が使えなかった。
以下の記事によると、vscodeではdocker上のファイルをみることができないらしい?ので、自分のPC内にdocker環境と同じバージョンのrubyとrubocop関連のgemをインストール必要があるとのことだった.

Ruby on Rails とかで docker 環境なのに #VSCode で ruby-rubocop 拡張を有効にするにはどうすれば良いのか? ( #Rails #Ruby )
https://qiita.com/YumaInaura/items/5cb1387bb738349c5d9e

解決策

自分のPC内に
ruby(2.6.6)
rails(6.1.0)
rubocop
rubocop-performance
rubocop-rails
をインストールした。

するとvscode上で以下のエラーメッセージが出てきた。

  • rubocop is not excutable
  • execute path is empty! please check ruby.rubocop.executePath

そこで、以下の記事を参考にexecutePathを設定した。

[Mac]VScode で ruby-rubocop が動かなくなったら executePath を設定してみよう
https://qiita.com/tommy_aka_jps/items/ca7ae36717b69a7e390b

すると、エラーメッセージが以下のように変わった。

  • rubocop.yml: Metrics/LineLength has the wrong name -spaceshould be Layout

そこで、以下の記事を参考にrubocop.ymlを書き換えた。

rubocop.yml
# MetricからLayoutに変更
Layout/LineLength:
  Max: 160
  Exclude:
    - "db/migrate/*.rb"

Ruby — rubocop のネームスペース変更に対応する
https://qiita.com/YumaInaura/items/53e14adaa5a00b6e59af

これで完了。

確認方法

最後に、適当なファイルに明らかに間違えた書き方を書いてrubocop側からエラーが返ってきたら導入完了。

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

vargrantからdockerへの移行

最近 M1 チップのマックになりました。
仮想環境がことごとく動かず、ようやく見つけた docker preview 7

別案件で、preview 7は利用していたのですが、またそれとは別な案件に手を出し、その案件が vargrantだったので、dockerに移行しました。
不満があるとすれば、preview 7は遅い。今までよりも数倍、遅いです。
30分くらいで終わるUnitTestが、5時間かかっても終わらないくらい。DBのアクセスがボトルネックになっているのかなー。という印象でした。

さて、Vargrant → docker でハマった部分について書いていきます。

運が良かった

vargrantのセットアップというよりも、実は仮想環境の中の話です。
DBサーバ、WEBサーバ、redisサーバを合いの子でセットアップしているような案件で、セットアップを行うためのシェルスクリプトが存在し、apt-getとか勝手にやってくれます。

ヤッホーと思い、そのままシェルを動かしたらdocker環境下では動かなかった部分があります。
ちなみに、OSはubuntuの13?とかだったのですが、ついでに18に移行しています。

Dockerfileは、そのシェルスクリプトをコピーして実行すだけの単純なやつです。

全般

  • Dockerfileから実行するので、sudo 全般を削除 (sudoが無い)
  • Dockerfileの中で、ENV DEBIAN_FRONTEND=noninteractiveを記述 (そうしないと途中で入力待ちになってしまいとまる)

タイムゾーンの変更

タイムゾーンを変更するのに、

timedatectl set-timezone Asia/Tokyo

となっていたのですが、dockerだとこのコマンドが使えないらしいので、

echo "Asia/Tokyo" > /etc/timezone
rm /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
dpkg-reconfigure -f noninteractive tzdata

と変更しました。

libpcre3と、dev

perl互換の正規表現エンジンです。
こいつ、こいつ、ubuntu18だと無いんですよね。古くなって使っちゃダメ的なやつ。
apt-getで入れようよすると、

libpcre3 is already the newest version (2:8.39-9).

とエラーになりインストールできませんでした。
代わりに、
libpcre2-32-0, libpcre2-dev, libpcre++-dev を入れました。

以上

nginxのビルドでライブラリが足りなかったりもしましたが、その辺は大した話でも無いし面白くも無いので割愛します。

でもやっぱり、docker遅いなー。ディスクアクセスなのかな。

#10 DONE 967.5s

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

railsとDockerで(Basic認証)環境変数

Docker上でBasic認証

Docker上でBasic認証を実装したがサイトに入ることができなかったため理由を考えてみた。

結論

超簡単 絶対に下まで読んでください。

Docker上で環境変数を設定してあげる

docker-compose.yml
version: '3'
services:
  web:
    environment:
      BASIC_AUTH_USER: 'admin' #仮のuser
      BASIC_AUTH_PASSWORD: '0000' #仮のpassword

理由として

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :basic_auth


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]# 環境変数を読み込む記述
    end
  end
end

大体上のような記述でBASIC認証を実装していると思う。
ターミナル上で環境変数を設定してあげて呼び出しているかもしれないが、Docker上でも環境変数を設定してあげないといけない
理由としてはDockerは今使っているpcとは別に仮想のマシンとしてコンテナを作りその上でアプリを動かしているから。

感想

一安心かと思ったがちょっと待ってほしい。

Gitにあげれなくないか。。?

今の状態でマージしてしまうとGitのリポジトリからBasic認証のuseridとpasswordが丸見えになっていてセキュリティも何もない。

なので

結論

docker-compose.yml
version: '3'
services:
  web:
    environment:
      BASIC_AUTH_USER: ${BASIC_AUTH_USER:-default}
      BASIC_AUTH_PASSWORD: ${BASIC_AUTH_PASSWORD-default}

上記のように書くことによってサーバーの環境変数を持ってきてセットすることができ、外部からは見えないようになる。
docker-compose.ymlファイルを触ったのでbuildも忘れずに

感想

初学者な為、知ってるわってあったらごめんなさい。。
やばい部分あったら教えていただけると幸いです!
マージする前に気づけてよかったのと、他のAPI使用する際にも活用できそう。

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

Mysql2::Error: Incorrect string valueのエラーの対処

はじめに

データベースを作成してマイグレーションも実行し、「いざ、データ(日本語)を入れて動かそう!」としたところで、以下のようなエラーが。。。

Mysql2::Error: Incorrect string value: '\xE3\x82\xB2\xE3\x82\xB9...' for column 'name' at row 1

どうやら文字コードが不適切らしい。。。
ということで、本記事では、このエラーについて、僕が試したことや解決策を書いていこうと思います!

技術・環境

Docker/docker-compose
ruby 2.7.2
rails 6.0.2.3

試したこと

テーブルの文字コードを調べてみました。

まずは、MySQLに接続する。

$ docker-compose run web rails db

次に、データベースの文字コードを調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

やはり、データベースの文字コードが「latin1」だった。。。
※MySQLはdefaultで「latin1」が文字コードに設定されています。

データベースの文字コードをutf8mb4に修正
僕はDockerを使用していたので、以下のようにdocker-compose.ymlを修正。
※以下の公式ドキュメントを参考にしました。
https://hub.docker.com/_/mysql

docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.7
    # この下の一行を追記しました
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: root
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql/volumes:/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:
      - .:/share-read
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  mysql-data:
    driver: local

データベースの文字コードを再度調べてみる。

> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

「よし!修正完了!」と思ったが、まだエラーが出てしまう。
もしかしたらテーブル自体の文字コードが「latin1」のまま?と思い調べてみると。。。

> SHOW CREATE TABLE users;

| users | CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  # 中略
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

やはり、テーブルの文字コードが変わっていなかった。。。

テーブルの文字コードをutf8mb4に修正
ということで、テーブルの文字コードを修正するのですが、10個くらいテーブルがあって一つ一つ修正するのは大変!
また、まだテーブルにデータを格納してなかったこともあり、今回はマイグレーションをやり直そうことにしました。

$ docker-compose run web rails db:reset
$ docker-compose run web rails db:migrate

ということで無事解決しました。
もし同じようなエラーが出てしまった方は、参考にしてみてください。

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

Django + jsonrpcserver + Docker Compose で作った JSON-RPC API を pytest-django の parametrize でテストする

以前、Django + jsonrpcserver + Docker Compose で作った "JSON-RPC 2.0" 準拠の API 動作環境に関する記事を投稿しました。
「Python の jsonrpcserver ライブラリを使ったら Django が JSON-RPC の API サーバになった」

今回はこの環境に pytest のを追加して parametrize で複数パターンの一括テストを行い、またテストクライアントの IP アドレスを偽装してみます。

pytest を使う(unittest ではなく)

Python / Django では unittest が標準のユニットテストフレームワークですが、ここではサードパーティの pytestpytest-django をインストールして使用します。

わざわざモジュールや設定を追加してでも pytest を使いたい理由はいろいろありますが、個人的にはテストコードの可読性向上に大きなメリットを感じています。

以下の 2 つのコードは、3 つの関数の戻り値がそれぞれ 0、0 以外、0 以上であることを unittest と pytest でテストする例です。

unittestの場合
class TestSamples(unittest.TestCase):
    def test_sample(self):
        self.assertEqual(func1(), 0)
        self.assertNotEqual(func2(), 0)
        self.assertGreaterEqual(func3(), 0)
pytestの場合
def test_sample():
    assert func1() == 0
    assert func2() != 0
    assert func3() >= 0

unittest のコードは JUnit 由来の伝統的な方式なので、テストクラスと比較条件ごとのメソッドを使用しますが、pytest は assert 文と比較演算子だけで簡潔なテストコードが書けます。

pytest-django のテスト環境を Docker Compose に追加する

以前の記事で作った動作環境に、pytest-django のテスト環境を追加します。

django_jrpc/
├── code/
│   ├── rpcproject/
│   │   ├── tests/
│   │   │   └── test_rpc.py  # 今回追加
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   ├── views.py   # 後で変更
│   │   └── wsgi.py
│   ├── manage.py
│   └── pytest.ini     # 今回追加
├── docker-compose.yml
├── Dockerfile
└── requirements.txt   # 変更

pytest.ini

pytest の設定ファイル pytest.ini を code/ の直下(manage.py と同じ階層)に作成します。

pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = rpcproject.settings
addopts = -p no:cacheprovider --cov --cov-branch

requirements.txt

下の 3 つのパッケージを追記します。

requirements.txt
Django==3.1.5
django-ipware==3.0.2
jsonrpcserver==4.2.0
pytest
pytest-cov
pytest-django
  • pytest: pytest 本体
  • pytest-django: pytest を Django で使うためのプラグイン
  • pytest-cov: カバレッジを出力するためのプラグイン

コンテナの再ビルドと起動

requirements.txt を保存したら、コンテナを再ビルドして起動します。

docker-compose up -d --build
  • django_jrpc/(docker-compose.yml があるディレクトリ)で実行する

Web ブラウザでlocalhost:8000/rpc/にアクセスして「Invalid JSON」エラーが表示されれば、リクエストが正しくルーティングされてjsonrpc関数が正常に動作しています。

invalid-json.png

テストコードを作成してリクエストを POST する

今回追加したファイル code/rpcproject/tests/test_rpc.py にテストコードを作成します。

レスポンスのステータスと JSON の戻り値をテストする

前回作った、2 つの整数の商と余りを返す関数 "division" をテストします。

テストクライアントのクラスとして django.test.Client を使用します。
参考: Django ドキュメント - テストクライアント

tests/test_rpc.py
import json

# テスト関数名は`test_`で始まる任意の名前
def test_division(client):
    # パラメータ名「client」で django.test.Client のオブジェクトを受け取る

    # POST する JSON-RPC リクエスト
    # この時点ではまだ Python の辞書型(dict)なので、「"divisor": 16,」と「"id": 1,」の
    # 各行末のカンマが許容される(JSON では不可)
    payload = {
        "jsonrpc": "2.0",
        "method": "division",
        "params": {
            "dividend": 1080,
            "divisor": 16,
        },
        "id": 1,
    }

    # JSON と下記のオプションを指定して POST リクエストを送り、API のレスポンスを得る
    response = client.post(
        path="/rpc/", data=payload, content_type="application/json-rpc", follow=True
    )
    # HTTP レスポンスのステータスコードを期待値(200 OK)と比較する
    assert response.status_code == 200

    # JSON-RPC レスポンスを辞書型に変換してから結果を取り出して、それぞれの期待値と比較する
    content = json.loads(response.content)
    assert content["result"]["quotient"] == 67  # 商
    assert content["result"]["remainder"] == 8  # 余り
  • client.post() のパラメータ
    • path="/rpc/": URL のlocalhost:8000に続くパスを指定します
    • data=payload: 上で定義した辞書型変数を POST データとして渡します
    • content_type="application/json-rpc": payload の辞書を変換して JSON-RPC リクエストを生成します
    • follow=True: リダイレクトが要求された場合に、True で自動的にリダイレクト先までフォローしてくれます。False にするとリダイレクトが要求された時点で終了するので、ステータスコードが 30x に、content が None になります。
JSON-RPCレスポンス
{
  "jsonrpc": "2.0",
  "result": {
    "quotient": 67,
    "remainder": 8,
    "ipaddress": "127.0.0.1"
  },
  "id": 1
}

pytest を実行してテスト結果とカバレッジを確認する

コンテナ名が不明の場合は、コマンドdocker-compose psで実行中のコンテナを確認します。

docker-compose ps
      Name                     Command               State           Ports
-----------------------------------------------------------------------------------
django_jrpc_web_1   python3 manage.py runserve ...   Up      0.0.0.0:8000->8000/tcp

表示されたコンテナ名を使って、以下のコマンドでコンテナの中に入ります。

docker exec -it django_jrpc_web_1 bash

コンテナの中でpytestコマンドを実行して、以下のようにテスト結果とカバレッジが表示されたら成功です。

pytestの出力
collected 1 item

rpcproject/tests/test_rpc.py .                                  [100%]

----------- coverage: platform linux, python 3.9.1-final-0 -----------
Name                           Stmts   Miss Branch BrPart  Cover
----------------------------------------------------------------
rpcproject/__init__.py             0      0      0      0   100%
rpcproject/settings.py            18      0      0      0   100%
rpcproject/tests/test_rpc.py       9      0      0      0   100%
rpcproject/urls.py                 4      0      0      0   100%
rpcproject/views.py               16      1      0      0    94%
----------------------------------------------------------------
TOTAL                             47      1      0      0    98%

========================= 1 passed in 0.82s ==========================

クライアントの IP アドレスと経路を偽装してテストする

あまり需要はないと思いますが、クライアントの IP アドレスや経路によって結果が変わる場合を想定して、リクエストの HTTP ヘッダを偽装してテストを行います。

偽装しないで同じコンテナ内でテストを実行すると、クライアントの IP アドレスは常に127.0.0.1になります。

直接接続のリクエストを偽装

tests/test_rpc.py
    response = client.post(
        path="/rpc/", data=payload, content_type="application/json-rpc", follow=True,
        REMOTE_ADDR="192.168.1.2"
    )

    content = json.loads(response.content)
    assert content["result"]["ipaddress"] == "192.168.1.2"

client.post()に HTTP ヘッダと同名のパラメータREMOTE_ADDRを追加します。上記は192.168.1.2のホストから直接接続された場合の偽装です。

中継されたリクエストを偽装

tests/test_rpc.py
    response = client.post(
        path="/rpc/", data=payload, content_type="application/json-rpc", follow=True,
        HTTP_X_FORWARDED_FOR="192.168.1.2,10.20.30.40,127.0.0.1"
    )

    content = json.loads(response.content)
    assert content["result"]["ipaddress"] == "192.168.1.2"

HTTP ヘッダHTTP_X_FORWARDED_FORと同名のパラメータにカンマ区切りの IP アドレスを指定することで、ロードバランサーなどで中継されたリクエストを偽装することができます。
カンマ区切りの先頭がクライアントの IP アドレスで、以降中継ノードのアドレスが続きます。

ここでテストしている関数 "division" が返すipaddressの値はクライアントのアドレスなので、上記のテストではカンマ区切りの先頭のアドレス192.168.1.2をアサートしています。
「DjangoでクライアントのIPアドレスを取得する」

@pytest.mark.parametrize で複数パターンをテストする

pytest の parametrize を使って、1 つのテストコードで複数のパターンをテストします。
参考: pytestのparametrizeの使い方とその有用性について

その前に views.py を変更して、クライアントの IP アドレスによって分岐する処理を追加します。
以下の例では、アドレスが192.168.1.*に一致しない場合はHTTP 400 Bad Requestを返して終了し、それ以外はこれまでどおりの動作になります。

rpcproject/views.py
from django.http import JsonResponse
from django.http.response import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from jsonrpcserver import method, dispatch

import ipware

@csrf_exempt
def jsonrpc(request):
    ipaddress, _ = ipware.get_client_ip(request)

    # クライアントの IP アドレスが 192.168.1.* でない場合は、HTTP 400 Bad Request を返す。
    if not ipaddress.startswith("192.168.1."):
        return HttpResponse(status=400)

    response = dispatch(request=request.body.decode(), context={'ipaddress': ipaddress})
    return JsonResponse(
        response.deserialized(), status=response.http_status, safe=False
    )

この変更によって192.168.1.*に一致しないクライアントに正しくBad Requestが返されることを複数のパターンでテストします。

以下は追加のテストコードの例です。
クライアントの IP アドレス192.168.1.2192.168.1.254でステータスコード 200 (OK) が、192.168.2.210.20.30.1で 400 (Bad Request) が返されることをテストします。

tests/test_rpc.py
import pytest

# HTTP_X_FORWARDED_FOR の値とレスポンスステータスのパターン
# 下のテスト関数で各タプルの値を、パラメータ x_header と http_status で受け取る
host_x_headers = (
    ("192.168.1.2,127.0.0.1", 200),
    ("192.168.2.2,127.0.0.1", 400),
    ("192.168.1.254,127.0.0.1", 200),
    ("10.20.30.1,192.168.1.1", 400),
)

@pytest.mark.parametrize("x_header, http_status", host_x_headers)
def test_client_address(client, x_header, http_status):
    payload = {
        "jsonrpc": "2.0",
        "method": "division",
        "params": {"dividend": 1080, "divisor": 16},
        "id": 1,
    }

    # タプルの値 x_header をパラメータ HTTP_X_FORWARDED_FOR で指定する
    response = client.post(
        path="/rpc/", data=payload, content_type="application/json-rpc", follow=True,
        HTTP_X_FORWARDED_FOR=x_header
    )
    # タプルの値 http_status がレスポンスステータス値と一致するかをテストする
    assert response.status_code == http_status
  • @pytest.mark.parametrize: テスト関数で pytest の parametrize を使うためのデコレータです
    • "x_header, http_status": host_x_headers内の各タプルで定義した 2 つの値を受け取る変数名を、カンマ区切りの文字列で定義します
  • def test_client_address(client, x_header, http_status):
    • x_header, http_status: デコレータで定義した名前のパラメータを使って、host_x_headersの各タプルの値を受け取ります

再度コンテナの中でpytestコマンドを実行して、テスト結果を確認します。

pytestの出力
collected 5 items

rpcproject/tests/test_rpc.py .....                              [100%]

----------- coverage: platform linux, python 3.9.1-final-0 -----------
Name                           Stmts   Miss Branch BrPart  Cover
----------------------------------------------------------------
rpcproject/__init__.py             0      0      0      0   100%
rpcproject/settings.py            18      0      0      0   100%
rpcproject/tests/test_rpc.py      16      0      0      0   100%
rpcproject/urls.py                 4      0      0      0   100%
rpcproject/views.py               19      1      2      0    95%
----------------------------------------------------------------
TOTAL                             57      1      2      0    98%

========================= 1 passed in 0.82s ==========================

テストが成功し、出力結果が5 itemsに増えたことから、タプルで定義したパターンごとに 1 つのテストとカウントされたことが確認できました。

今後の予定

盛り込みすぎると読み返しにくくなるので、以下は今後の予定にします

  • VS Code Remote Containers で開発・デバッグ環境をコンテナ化
  • pylint-django + black + rope によるリント、フォーマット、リファクタリング環境
  • Pylance を使った静的型付け
  • Model とデータベースの使用
  • フィクスチャを使ったテストデータの投入
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker+laravelでGeneral error: 1812 が発生

初めに

docker + laravelを使って、なんかプログラムを作ろうと
migrateを行った際に

$ docker exec -it docker-sample_php-fpm_1 bash
root@39aae0ad8da6:/var/www/html# php artisan migrate

   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 1812 Tablespace is missing for table `laravel`.`migrations`. (SQL: select `migration` from `migrations` order by `batch` asc, `migration` asc)

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667|         // If an exception occurs when attempting to run a query, we'll format the error
    668|         // message to include the bindings with SQL, which will make this exception a
    669|         // lot more helpful to the developer instead of just the database's errors.
    670|         catch (Exception $e) {
  > 671|             throw new QueryException(
    672|                 $query, $this->prepareBindings($bindings), $e
    673|             );
    674|         }
    675| 

      +27 vendor frames 
  28  artisan:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput)) 

が発生した。
備忘録として、メモしておく

何が起こったか?

出ているエラーをよくみてみると

Tablespace is missing for table `laravel`.`migrations`

と出ている。
dokcerのmysqlのコンテナーにログインして、テーブルを確認すると

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)

mysql> use laravel;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------+
| Tables_in_laravel |
+-------------------+
| failed_jobs       |
| migrations        |
| password_resets   |
| tasks             |
| users             |
+-------------------+
5 rows in set (0.00 sec)

migrationsテーブルは存在する。
migrationsテーブルの中身を見てみる。

mysql> select * from migrations;
ERROR 1812 (HY000): Tablespace is missing for table `laravel`.`migrations`.

エラーが出てくる。

解決

色々と調べてみたが、いかが手っ取り早そう

mysql> drop table migrations;
Query OK, 0 rows affected (0.02 sec)

他のテーブルでもmigrateが詰まったら
drop table tbl_name

を実行すれば良さそう。

それでは素敵なdocker lifeを

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

Dockerでお手軽JSONデータベース体験 ~ Couchbase Server最新版を試す

はじめに

本記事の対象者

本記事の対象者として、以下の様な人を想定しています。

  • JSONデータを格納できるデータベースで開発を始めたい(中でも特に以下の関心がある人)
    • JSONデータへの問合せをSQLのようなクエリ言語で行いたい
    • ミリ秒以下の応答性能を実現したい
  • 開発・検証用にローカル環境にデータベースをインストールしたい

インストール方法について

表題にある様に、ここではDockerを利用します。
Dockerについての知識を前提としなくても、実行できる記述を心がけますが、インストーラーによるインストールの方が馴染みがある方は、下記からダウンロードして実行してください(Mac, Windows, Ubuntuのインストーラがあります)。その後、初期設定などについて、本記事をご参照ください。

COUCHBASE FREE NOSQL DATABASE LIST

Dockerについては、環境にインストールされているところから、解説を始めます。

Couchbase Serverについて

Couchbase Serverは、分散アーキテクチャーを持ちますが、ここではローカル環境に、シングルノードのクラスターとして構築します。(Dockerを使えば、ローカル環境で複数のノードからなるクラスターとして構築することも可能です。Dockerに慣れている方には特に難しいところはありませんが、興味のある方は、参考情報のドキュメントをご参考ください)

Couchbase Serverには、Enterprise EditionとCommunity Editionがありますが、ここでは、検証目的として、Enterprise Editionを用います(上記リンクに各エディションの利用できる範囲について記述があります)。

環境構築手順

Dockerによるインストール

初回実行

下記のコマンドを実行します。

$ docker run -d --name db -p 8091-8094:8091-8094 -p 11210-11211:11210-11211 couchbase

CouchbaseのDockerリポジトリから最新版("couchbase:latest")がダウンロードされ、コンテナの実行が開始されます。

http://localhost:8091にアクセスします。

下記の様な画面が表示されます。

image.png

インストール完了です!

インストール確認

...というのも味気ないので、インストールされた状態を確認してみます。

ログの確認

dbは、docker runコマンドの--nameオプションで指定したものです。

$ docker logs db
Starting Couchbase Server -- Web UI available at http://<ip>:8091
and logs available in /opt/couchbase/var/lib/couchbase/log

イメージの確認

$ docker images
REPOSITORY                                                TAG                 IMAGE ID            CREATED             SIZE
couchbase                                                 latest              8f1b63be0df7        3 weeks ago         1.19GB

プロセスの確認

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                                               NAMES
d4bfe9191a55        couchbase           "/entrypoint.sh couc…"   6 minutes ago       Up 6 minutes        8095-8096/tcp, 0.0.0.0:8091-8094->8091-8094/tcp, 11207/tcp, 0.0.0.0:11210-11211->11210-11211/tcp, 18091-18096/tcp   db

「couchbase」という名前のDockerイメージがインストールされ、そのイメージから「db」という名前のコンテナが作られ、稼働中の状態であることが確認されました。(最後にこのコンテナの停止と再起動についてもみていきます)

Couchbase Serverの初期設定

クラスター初回構築

ウィザード選択

上記の画面イメージに見える通り、インストール直後にWEBコンソールにアクセスした際の初めの選択肢は、「Setup New Cluster」と、「Join Existing Cluster」との二つです。ここでは、当然前者を選択します。

次の様な画面が表示されるので、適宜入力します。「Next: Accept Terms」を押します。

image.png

Terms and Conditions

適宜、チェックボックス押下します。
image.png

「Finish With Defaults」で終了することもできますが、「Configure Disk, Memory, Services」をみてみましょう。

クラスター設定

image.png

image.png

検証目的において、特に変更しなければならないところはないので、「Save & Finish」を押下し、終了します...と言いたいところですが、下記の様なエラーが表示されるかもしれません。
image.png

上の画面を見直すと、TOTAL QUOTAが、Max Allowed Quotaを上回っていることがわかります。

image.png

適宜、各サービスのQuotaを変更し、Max Allowed Quota以下になる様に調整します。また、不要なサービスのチェックボックスを外すことにより、このノードでは、そのサービスが無効になります。後から有効にすることはできませんが(その場合は、クラスターからの削除、再追加が必要)クラスター全体の構成を柔軟に設計することができます。ここで準備する検証環境としては、特に必要のない限り、Search, Analytics,Eventingを無効にして差し支えないかと思います。

最終的にこの様な画面が表示されました。
image.png

バケット作成

データの格納の単位であるBucketを作るため、「Bucket」リンクをクリックします。

表示されるのは、バケット一覧画面です。右上の「ADD BUCKET」リンクをクリックします。

image.png

適当な名前を入力して「Add Bucke」ボタンを押下します。(バケットの設定は後から変更可能です)

image.png

image.png

こんな感じになったかと思います。「Warning」が表示されていますが、これは、デフォルトでレプリカの設定が有効になっているためです。アラートが表示されない様にするため「Edit」を押下します。

「Advanced bucket settings」を展開し、Replicasのチェックボックスを外します。
image.png

ついでに、Flushを有効にしておきます。「Save Changes」を押下します。

image.png

「Warning」が消え、「Flush」ボタンが追加されました。

image.png

ドキュメント追加

ドキュメントの追加を行ってみます。
上の画面の右上のDocumentsリンクをクリックします。

この様な画面が表示されます。ブラウザの画面サイズを十分に広げることで、左のメニューが表示されます。
右上の「ADD DOCUMENT」をクリックします。

image.png

適当なドキュメントIDを入力します。
image.png

WEBコンソールからドキュメントを作った際のデフォルトの内容が表示されるので、適宜編集するなりして、「Save」を押下します。(ドキュメント作成確認のためなので、修正は不要です)
image.png

ドキュメントの追加が確認されました。

image.png

コンテナの停止・起動

コンテナをストップします。

$ docker stop db

実行中のコンテナを確認します。ブラウザでWEBコンソールにアクセスできないことも確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

実行されていないコンテナを確認します。

$ docker ps -a
CONTAINER ID        IMAGE                                                         COMMAND                  CREATED             STATUS                          PORTS                                                                                                      NAMES
d4bfe9191a55        couchbase                                                     "/entrypoint.sh couc…"   49 minutes ago      Exited (0) About a minute ago                                                                                                              db

コンテナを改めて開始してみます。

$ docker start db

WEBコンソールにアクセスできることを確認します。

image.png

お疲れ様でした!

...というほどカロリーを使ってはいないと思いますが、新しい技術に初めて触れる時の抵抗感が多少でも緩和されたのであれば幸いです。

この後は、下記の記事なども参考にしてみてください

次のステップ:Couchbase Serverを用いた開発

リアクティブプログラミングをNoSQLで「体験」してみる(Couchbase Reactive APIに触れる)

JavaScript開発にNoSQLデータベースを活用する(CEANスタック紹介) ~ Node.js + Couchbaseアプリ開発 ステップバイステップガイド 2

Go + NoSQL(Couchbase) アプリ開発 ステップバイステップガイド (1)

Node.js + NoSQL(Couchbase) アプリ開発 ステップバイステップガイド (1)

参考情報

Couchbaseローカル環境をDockerで構築
以前のバージョン(4.5.1)のCommunity Editionを使うケースが紹介されています。また、Dockerのカスタマイズについても解説されています。

Deploy a Multi-Node Cluster with Containers
本記事冒頭で触れたDockerを使って複数ノードのクラスタをローカル環境で実現する方法(同じことを実現するために、Kubernetesを使う方法もありますが、それはまた別のトピックとして、いずれ記事にできたらと思います)

よく使うDockerコマンド
各種Dockerコマンドが簡潔に紹介されています。

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

【Docker】コマンド一覧

[] 内は省略可能

  • コンテナを作成し起動 [バックグラウンドで実行]
docker-compose up [-d]
  • コンテナ起動(既にコンテナがある状態で)
docker start <コンテナ名>
  • コンテナ停止
docker stop <コンテナ名>
  • 全てのコンテナ停止
docker stop $(docker ps -q)
  • コンテナ起動状況確認 [停止しているコンテナも取得]
docker ps [-a]
  • イメージ実行
docker run <イメージ>
  • イメージ一覧表示
docker images
  • サービスのビルドを実行
docker-compose build
  • 実行中コンテナ内で、新しいコマンドを実行
docker exec [オプション] <コンテナ> <コマンド> [引数]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ARM64】New RelicでDockerサーバー監視 on Docker on RasPi4

最近Raspberry Pi 4B(4GB)上のDockerでサービスを次から次へと立ち上げていて、サーバー監視してみたくなってきたのでやり方をメモ。

  • Dockerに対応したサーバー監視ツール
  • ARMアーキテクチャに対応
  • サーバー監視そのものもDocker上で行いたい

という欲張りな人には役立つはず。

やること

Raspberry Pi 4B(4GB)上で動いているサーバーの状態(CPU・メモリ・プロセス)を監視する。

環境

nginx-proxyを使って各サーバーへのリクエストを振り分けている。

New Relic について

サーバー監視ツールはいくつもありますが、その中でDockerに対応しているツールとなると限られてくる。サービスの選定はこちらの記事を参考にして、SaaS型で手軽に無料で使えそうなNew Relicを選んだ。
サーバー監視ツールは価格が高く、個人開発でちょろっと公開しているだけのサービスに導入するのは敷居が高い気がしていたが、New Relic Oneでは毎月100GBのデータ取り込みまでは無料ライセンスで使える。個人で使う分であれば十分すぎる容量だ。
New Relicにはアプリのパフォーマンス監視(APM)やPingによる死活監視など複数のサービスがあるが、今回使うのはCPUやメモリの使用量を見られるInfrastructure。

アカウント作成・ライセンスキー取得

New Relicのサイトにアクセスして、右上にある「無料サインアップ」を押してアカウントを作る。アカウントが作成できたら、右上のアカウントのドロップダウンからAccount settingsを選択し、左のサイドバーからAPI keysをクリックする。
Create keyを押して、Key typeIngest - LicenseNameを任意の名前にしてキーを作成する。

Dockerコンテナを立てる

x86-64であれば公式にDockerイメージが提供されているので、それを公式のドキュメントに従ってコンテナを起動すればいいだけなのだが、arm64なので自前でビルドする必要がある。

Dockerイメージのビルド

とはいえ、イメージの中に突っ込まれるnewrelic-infranewrelic-infra-ctlnewrelic-infra-serviceの3つのバイナリを差し替えればいいだけ。
バイナリは公式に提供されているので、以下のリンクよりダウンロード(2021年1月現在では1.9.7が最新)。
https://download.newrelic.com/infrastructure_agent/binaries/linux/

mkdir ~/newrelic-infra-setup
cd ~/newrelic-infra-setup
wget https://download.newrelic.com/infrastructure_agent/binaries/linux/arm64/newrelic-infra_linux_1.9.7_arm64.tar.gz
tar -zxvf newrelic-infra_linux_1.9.7_arm64.tar.gz

次にnewrelic/infrastructure-agentのGitHubリポジトリをCloneする。

git clone https://github.com/newrelic/infrastructure-agent.git

Dockerイメージをビルドする方法はこのページを参考にした。まずダウンロードしたバイナリを指定のフォルダにコピーする。linux_amd64にarm64バイナリを入れるというのもアレだが、そこはスルーで。

mkdir -p infrastructure-agent/target/bin/linux_amd64
cp newrelic-infra/usr/bin/* infrastructure-agent/target/bin/linux_amd64/
cd infrastructure-agent/build/container
export IMAGE_VERSION=1.0  # 適当に。指定しなければ0.0になる
make build/base

Dockerコンテナを立てる

これでDockerイメージが生成されたので、後はx86-64と同じ手順でコンテナを立てる。手順はこのページに従えばいい。

最初に取得したライセンスキーをnewrelic-infra.ymlに入れます。

cd ~/newrelic-infra-setup
echo "license_key: {YOUR_LICENSE_KEY}" > newrelic-infra.yml
touch newrelic-infra.dockerfile
vim newrelic-infra.dockerfile  # どのエディタでも大丈夫
newrelic-infra.dockerfile
FROM newrelic/infrastructure:latest
ADD newrelic-infra.yml /etc/newrelic-infra.yml
touch docker-compose.yaml
vim docker-compose.yaml  # どのエディタでも大丈夫
docker-compose.yaml
version: '3'

services:
  agent:
    container_name: newrelic-infra
    build:
      context: .
      dockerfile: newrelic-infra.dockerfile
    cap_add:
      - SYS_PTRACE
    network_mode: host
    pid: host
    privileged: true
    volumes:
      - "/:/host:ro"
      - "/var/run/docker.sock:/var/run/docker.sock"
    restart: unless-stopped
docker-compose build
docker-compose up -d

これでもうサーバー監視がもう始まっているので、New RelicのWebサイト上の上部バーからInfrastructureを選んでみると、CPU使用率や空きメモリの割合等がグラフで表示されている。

Screenshot_2021-01-12 Infrastructure New Relic One.png

また、上部バーからEntity explorerを開いてみると、他にも色々なグラフや表があるので、好きなものをダッシュボードに追加すると良い。

Screenshot_2021-01-12 Docker New Relic One.png

newrelic-infraが一番CPU使ってますね、草。

不具合

  • Infrastructure > Hosts > Docker Containers でコンテナごとの情報が取得されていない
  • docker-compose logsするとエラーを吐いている("integration exited with error state"

Docker対応ということでNewRelicを選んだのに使えないのは本末転倒なのでなんとか直したい。

参考文献

New Relic Pricing(日本語)
New Relic license key
Docker container for infrastructure monitoring
Docker instrumentation for infrastructure monitoring
newrelic/infrastructure-agent
newrelic/infrastructure-agent - Containerized Agent
newrelic/infrastructure-agent - ARM64 (aarch64) support #107
Dockerコンテナの監視を行うサービス
「デジタル・ニューノーマル構想」を掲げるNew Relicが製品や価格体系を刷新

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

Rust公式ドキュメントをDockerコンテナ上で進めてみる(1. 事始め)

はじめに

タイトルの通りです。
Rustは初めてです。
コンテナの使い方として、以下を考えましたが、今回のコンテナをrustの学習以外で使うことはないため、前者でいくことにしました。

  • FROM:rustとする
  • FROM:ubuntuなどにし、rustをその中でインストールする方法

前者の場合、rustイメージが利用しているosが使われます。使っているosはrustのDockerfileで確認。
https://github.com/rust-lang/docker-rust

参考にしたもの

Rust公式ドキュメント(The Rust Programming Language 日本語版):https://doc.rust-jp.rs/book-ja/title-page.html
DokerHub(Rust):https://hub.docker.com/_/rust

1.1 インストール

公式ドキュメント:https://doc.rust-jp.rs/book-ja/ch01-01-installation.html
今回はrustイメージを使ってしまうので、ここはスキップ。
代わりに、dockerHub上にrustのimageがあることを確認。

DockerHub上のRustのDockerfileのサンプル
FROM rust:1.31

WORKDIR /usr/src/myapp
COPY . .

RUN cargo install --path .
CMD ["myapp"]

サンプルがあるならと、バージョンのみ変えてこの通りに記述してみましたがbuildでエラーになりましたのでみなかったことにします。

Cargo.tomlがなんだかわからん
$ docker build -t my-rust-app .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM rust:latest
 ---> 8d0d57072949
Step 2/5 : WORKDIR /usr/src/myapp
 ---> Using cache
 ---> 6015e37ea342
Step 3/5 : COPY . .
 ---> 66589dbef629
Step 4/5 : RUN cargo install --path .
 ---> Running in b3cdc5e8eece
error: `/usr/src/myapp` does not contain a Cargo.toml file. --path must point to a directory containing a Cargo.toml file.

1.2 Hello, World!

公式ドキュメント:https://doc.rust-jp.rs/book-ja/ch01-02-hello-world.html
Dockerfileを作成します。一部、DockerHub上のuse imageの仕方に合わせています。

Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects/hello_world
COPY . .

RUN cd /usr/src/projects/hello_world && \
    rustc main.rs
CMD ["./main"]

コンテナにコピーするmain.rcを作成します。

main.rc
fn main() {
    // 世界よ、こんにちは
    println!("Hello, world!");
}

ビルドして実行

$ docker build -t my-rust-app .
$ docker run -it --rm --name my-running-app my-rust-app
Hello, world!

1.3 Hello, Cargo!

公式ドキュメント:https://doc.rust-jp.rs/book-ja/ch01-03-hello-cargo.html
Dockerfileを編集していきます。

Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects

RUN cd /usr/src/projects && \
     cargo new hello_cargo --bin && \
     cd /usr/src/projects/hello_cargo && \
     cargo build

CMD ["/usr/src/projects/hello_cargo/target/debug/hello_cargo"]

ビルドしたところUSERを指定してと言われる。

$ docker build -t my-rust-app .
(中略)
Step 5/7 : RUN cd /usr/src/projects &&     cargo new hello_cargo --bin
 ---> Running in 1f2404887764
error: Failed to create package `hello_cargo` at `/usr/src/projects/hello_cargo`

Caused by:
  could not determine the current user, please set $USER

RUN,CMDをコメントアウトしてコンテナ内に入って実行しても同様。
Dockerはユーザーを指定しないとrootでログインしますが、echo $USERは空。

$ docker run -it --rm --name my-running-app my-rust-app
root@126e1954fec0:/usr/src/projects# cargo new hello_cargo --bin
error: Failed to create package `hello_cargo` at `/usr/src/projects/hello_cargo`

Caused by:
  could not determine the current user, please set $USER
root@126e1954fec0:/usr/src/projects# echo $USER

環境変数のUSERを定義するようDockerfileを修正します。

Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects

ENV USER=root
RUN cd /usr/src/projects && \
     cargo new hello_cargo --bin && \
     cd /usr/src/projects/hello_cargo && \
     cargo build

CMD ["/usr/src/projects/hello_cargo/target/debug/hello_cargo"]

ビルドして実行

$ docker build -t my-rust-app .
$ docker run -it --user root --rm --name my-running-app my-rust-app
Hello, world!

おまけ

1.1 インストールで出ていたDockerHubのrustテンプレートのビルド時に発生した以下のエラーを解消したい場合

FROM rust:latest
WORKDIR /usr/src/myapp
COPY . .

##追加###############
ENV USER=root
RUN cargo init .
####################

RUN cargo install --path .
CMD ["myapp"]

参考:https://kajirikajiri.gitbook.io/gitbook/untitled

おわりに

Docker上で言語の勉強をしていますがローカルのパソコンの環境を汚さなくて良いので、なんども作り直しができるのは本当に良いですね。
また、USERの件など、ドキュメントをそのままなぞっているだけでは気付きづらいRustの仕様もDocker上で進めていることによりエラーが出たりして思わぬところで理解が深まる効果が得られています。

なお、実際は、FROMとWORKDIRの定義といった最低限のDockerfileのみでコンテナを作成してコンテナに入り、ドキュメントの通りにコマンド実行したり作成されたものの中身など見ながら進めています。
問題なさそうになったら、Dockerfileを更新して最終的にCMDで同じ結果になることを確認する手順を踏んでいます。
Rustの勉強だけならDockerfileを更新する必要ないのですが、Dockerの仕様の勉強にもなるためしばらく続行しようと思います。

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

Dockerが注目される理由。そもそもDockerとは?その使い方

はじめに

 本記事は、Dockerの初学者を対象に基礎的な内容を学ぶための記事。

※追伸:こちらの記事よりも更に良い記事を見つけてしまったので、もっと詳しく知りたい方は以下をご参照ください。
・Dockerでプログラマが最低限知るべきことが、最速でわかるチュートリアル

Dockerとは

Dockerは仮想化の一つで、コンテナ型仮想化と呼ばれている。
コンテナ型仮想化はOSの上に「コンテナ」と呼ばれる仮想的な空間を作ることでOSの上に仮想的なOSを展開して、更にプロセスの実行空間を区分け(隔離)できる。

仮想化サーバーとの違い・長所

  • 起動速度がはやい
  • 移動が可能
  • 稼働が軽い
  • リソースが有効活用可能

Dockerが注目されている理由

 一番の理由は、移動ができてリソースを有効活用できるということである。未使用のコンピュータ資源をアクティブに入れ替えながら使うことでサーバの効率化ができることから、クラウドサービスといった変動費の費用が発生するものと相性が良い。Google社では全部のソフトウェアをコンテナに乗せており、アクティブに入れ替えながら使うことでサーバの効率化をしている。

 他には従来、何かサービスをつくる場合、エンジニアは環境の制約を大きく受けていた。自分が書いたコードが本当にサーバー上で動くのか心配しながら開発を行わなければらなかった。しかし、Dockerを使うことで、自身のPCなどのdockerでコンテナを実行してながら開発をすることで、コードを仕上げ、最終的にコンテナを移動させるだけで、アプリケーションを動く状態にできるのだ。
 このことから、Dockerはインフラエンジニアのスキルに止まらず、
 →プログラマーの必須科目と言われているのである。

Dockerを使う流れ

1Dockerインストール
・OS上にDockerをインストールします。macやWindows、Linuxにインストールできる。
2Dockerfileの作成
・Dockerfileは、全く新しいDocker Imageを作成するための手順を決めるテキストファイル。したがって、インターネットから取ってくる場合など、全く新しいDocker Imageの作成をしない場合は不要。
3Docker Imageの作成
・コンテナイメージを任意の場所から持ってきて作成します。Dockerインストール後初期段階では、当然コンテナイメージは存在しないので、インターネット上からコマンド入力で持ってきます。
・インターネット上の一般的なイメージ格納場所として、「Docker Hub」というところがあります。他には、AWSのECR、さまざまな企業に独自のリポジトリサーバーに構築されて格納・管理されています。
4コンテナ起動

実際のコマンド入力

・インターネットからイメージを取得する

$ docker pull [イメージの名前]

Docker Hubでアカウントをつくり、サービス名を検索することで、コピ&ペーストできるコマンドを見つけることもできます。Copy and paste to pull this imageのところ。
image.png

例)Apacheのイメージ取得

$ docker pull httpd

・イメージが取れたのか確認

$ docker images

・docker起動
→オプションについて
① -d バックグラウンドでdockerを実行する
② -p ポートの指定。[8088:80]は[ホストのポート:コンテナのポート]の関係。コンテナのポート80番をホスト側の8088で待ち受けるという意味になる。
※docker runコマンドはone liner(一行だけ)で実行でき、イメージを取得するコマンドを省略して実行できる。その場合は、自動で指定のイメージをローカルから参照する。イメージが無かったらDocker Hubから取ってきてくれる。

$ docker run -d -p 8088:80 httpd

・コンテナの状況を見る
→コンテナIDやSTUTASの項目を見ることができる。

docker ps -a

・コンテナ停止

$ docker stop [コンテナID]

・コンテナ自体の削除

$ docker rm [コンテナID]

・イメージの削除

$ docker rmi [コンテナID]

おわりに

 今回の内容ではDockerの基礎的な知識をまとめた。
 応用としては、Dockerfileの作成やdocker-composerを使い複数のコンテナを管理することなど、オーケストレーションツールを使って、複数のDockerのリソース管理をするといった内容がある。

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

Dockerでngrokを利用する

ngrokとは

ngrokは、ローカルホストを外部に公開(トンネル)できる

やりかた

Docker Hubにwernight/ngrok(https://hub.docker.com/r/wernight/ngrok/)というイメージがあります。
ngrokのイメージの中で多く利用されており、使いやすいです。

docker-compose.yml
version: "3.8"

networks:
  default:

services:
  nginx:
    image: nginx:stable-alpine
    container_name: nginx
    ports:
      - 80:80
    networks:
      - default
    depends_on:
      - app

  ngrok:
    image: wernight/ngrok:latest
    ports:
      - 4040:4040
    environment:
      NGROK_PROTOCOL: http
      NGROK_PORT: nginx:80
     #NGROK_AUTH: ${NGROK_AUTH}
    depends_on:
      - nginx
    networks:
      - default

上記は、nginxのコンテナをそのままngrokで公開しています。
environmentNGROK_PORT: nginx:80のように、公開するコンテナ名とポートを指定するだけです。NGROK_AUTHは認証が必要なサービスを利用しない場合は不要です。

便利だし、サクッと使えるので助かる...!
LinebotのようなSSL必須の自己証明不可の外部APIをローカルで開発する場合にはすごく役立ちそうです。

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