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

Macbook Pro M1(Apple Silicon) で Dockerを動かす

準備

※英語わかんなくてもなんとかなります。

  1. DockerのDeveloper Preview Programに参加する
  2. Docker Community Slackに参加する
  3. Developer Preview Program の案内メールが来るのでDocker Community SlackのDisplay Nameを伝える
    • #dev-preview-programに招待される
  4. #dev-preview-programのpin付けされているメッセージを読んでDokcerのPreview5を入手する
    • ここのメッセージに注意事項もセットで記載されてます

ここから先は特に特殊なことは書いてないです。


インストール

  • dmgファイルを実行
  • いつもの手順でApplicationsへ入れる(PREVIEWマーク付いてる!)

image.png

コンテナ起動

  • Docker.appを実行
    • 特に問題発生せず

その他

  • あっけなく動いてしまった
  • 時間かかるのはPreview版を入手するところまで
  • 動かないイメージはちらほら(手元で使ってたものは3割動かなかった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ubuntu 20.04.1 上でdockerを使うための設定スクリプト (プロキシ切り替え対応)

ubuntu 20.04.1 上でdockerを使うための設定スクリプト (プロキシ切り替え対応)

ubuntu 20.04.1 上でプロキシ環境でdockerを使うための設定スクリプト という記事を書きましたが、プロキシなし環境に切り替えることができるスクリプトを作成しました。

スクリプト

https://gist.github.com/m-tmatma/7556d81a850a8145ded7074578722334

準備

  1. setup-docker.py という名前で保存します。
  2. chmod +x setup-docker.py で実行権限を付けます。

使い方

ヘルプ

$ ./setup-docker.py 
usage:
sudo ./setup-docker.py set http://proxy:port
sudo ./setup-docker.py noproxy

指定したプロキシを設定する

http://192.168.11.61:3128 に設定する場合

sudo ./docker-proxy.py http://192.168.11.61:3128

プロキシなしでアクセスする場合

sudo ./docker-proxy.py noproxy

スクリプト本体

https://gist.github.com/m-tmatma/7556d81a850a8145ded7074578722334

#!/usr/bin/python3

import string
import subprocess
import os
import sys

###########################################################################
#  template for /etc/apt/apt.conf
###########################################################################
template_apt_conf = """\
Acquire::http::Proxy "$http_proxy_url";
Acquire::https::Proxy "$https_proxy_url";
"""


###########################################################################
#  template for /etc/systemd/system/docker.service.d/override.conf
###########################################################################
template_docker_service_override = """\
[Service]
Environment="HTTP_PROXY=$http_proxy_url"
Environment="HTTPS_PROXY=$https_proxy_url"
Environment="NO_PROXY=localhost,127.0.0.1"
"""

###########################################################################
#  template for ~/.docker/config.json
###########################################################################
template_config_json = """\
{
  "proxies": {
    "default": {
      "httpProxy": "$http_proxy_url",
      "httpsProxy": "$https_proxy_url",
      "noProxy": "localhost,127.0.0.1"
    }
  }
}
"""


###########################################################################
#  write proxy data to a file
###########################################################################
def write_proxy(file_name, input_template, http_proxy_url, https_proxy_url):
    if http_proxy_url or https_proxy_url:
        context = {
            'http_proxy_url' : http_proxy_url,
            'https_proxy_url' : https_proxy_url
        }
        template = string.Template(input_template)
        data = template.substitute(context)

        with open(file_name, "w") as fout:
            fout.write(data)
        print("wrote: " + file_name)
    else:
        if os.path.exists(file_name):
            os.unlink(file_name)
        print("removed: " + file_name)

###########################################################################
#  get the home directory of the original user
###########################################################################
def get_original_home():
    user = os.environ['SUDO_USER']
    if user:
        home_dir = os.path.expanduser('~' + user)
    else:
        home_dir = os.path.expanduser('~')
    return home_dir

###########################################################################
#  write etc/apt/apt.conf
###########################################################################
def write_apt_conf(http_proxy_url, https_proxy_url):
    write_proxy('/etc/apt/apt.conf', template_apt_conf, http_proxy_url, https_proxy_url)

###########################################################################
#  write /etc/systemd/system/docker.service.d/override.conf
###########################################################################
def write_docker_service_override(http_proxy_url, https_proxy_url):
    docker_service_d = '/etc/systemd/system/docker.service.d'
    if not os.path.isdir(docker_service_d):
        os.mkdir(docker_service_d)

    override_conf = os.path.join(docker_service_d, "override.conf")
    write_proxy(override_conf, template_docker_service_override, http_proxy_url, https_proxy_url)

###########################################################################
#  write ~/.docker/config.json
###########################################################################
def write_docker_config(http_proxy_url, https_proxy_url):
    # get the path of ~/.docker
    home_dir = get_original_home()
    docker_dir = os.path.join(home_dir, ".docker")

    # create ~/.docker
    if not os.path.isdir(docker_dir):
        os.mkdir(docker_dir)

    # get ~/.docker/config.json
    config_json = os.path.join(docker_dir, "config.json")
    write_proxy(config_json, template_config_json, http_proxy_url, https_proxy_url)

###########################################################################
#  usage
###########################################################################
def usage():
    script = sys.argv[0]
    print("usage:")
    print("sudo " + script + " set http://proxy:port")
    print("sudo " + script + " noproxy")

def exec(command):
    subprocess.call(command.split(), shell=False)
    print("ran: " + command)

if __name__ == '__main__':
    if len(sys.argv) < 2 or os.getuid() != 0:
        usage()
        sys.exit(1)

    http_proxy_url = None
    https_proxy_url = None
    cmd = sys.argv[1]
    if cmd == "set":
        if len(sys.argv) == 3:
            http_proxy_url = sys.argv[2]
            https_proxy_url = sys.argv[2]
        else:
            usage()
            sys.exit(1)
    elif cmd == "noproxy":
        pass
    else:
        usage()
        sys.exit(1)

    exec('apt install -y docker.io docker-compose')

    write_apt_conf(http_proxy_url, https_proxy_url)
    write_docker_service_override(http_proxy_url, https_proxy_url)
    write_docker_config(http_proxy_url, https_proxy_url)

    exec('systemctl daemon-reload')
    exec('systemctl restart docker')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Pi4 3台でKubernetesクラスタ構築

ALHアドベントカレンダー2020
12/12(土)はきくりんが担当いたします!!
サーバー、スイッチ、電源、筐体の選定から、組み込み、構築、ナレッジ化とインフラエンジニア総合格闘技的な内容となりました。
IMG_7989.jpg

目的と背景

ずっとやろうと思っていたラズパイのクラスタ構成を使ったKubernetesの構築ですがこの度ついに着手出来たので情報まとめていきます。
とりあえずクラスタ構成の構築と、webサーバが上がることをゴールとします。
今回クラスタケースの収容数が4つのため、既に持っていたラズパイ3も収容したいと思います。
スイッチングハブと充電器も収容することを考えると3台がMAXだよね、ということに組んでから気付いたので3台構成とします。
一部写真で同居していますがまあ気にしないでください。

構築・ハードウェア編

準備したもの

・新規購入
IMG_7944.JPG
※主張の激しいマウスは無関係です。
・後で追加+家に転がってたもの
IMG_7965.jpg

一覧(工具類は割愛)

パーツ名 詳細 備考
Raspberry Pi4 Raspberry Pi 4 Model B/4GB x 3台 k8sクラスタ用
Raspberry Pi3 Raspberry Pi 3 Model B?/1GB x 1台 検証用、3台構成に変更したので、結局使わなかった
Raspberry Pi用ヒートシンク Akuoly Raspberry Piアルミ製ヒートシンクセット ラズベリーパイ冷却キットクーラーheatsink 15点セット x2 k8sクラスタ用、どうやら3向けだったみたいで1セット
SDカード Samsung micro SDXC 64GB MB-MC64GA x 3枚 k8sクラスタ用
スイッチングハブ BUFFALO Giga対応 プラスチック筐体 AC電源 5ポート LSW6-GT-5EPL/NBK ブラック スイッチングハブ ローコストモデル 簡易
スイッチングハブ ロジテック 1000BASE-T対応 スイッチングハブ LAN-GSW05PSBE 廃盤のため中古。買ってから3ポートしか無いことに気づき、結局使わなかった
ケース GeeekPi Raspberry Piクラスタケース(4層)
冷却ファン EASYDIY 120mm PWM 高風量静音デュアルフレーム白色LEDケースファン ケース付属が虹色に光るのが嫌なので
ファンコントローラ サイズ ファンコン搭載 ファン用USB変換ケーブル AS-71G2 風量とLEDの光量調節用
USB充電器 Anker PowerPort Speed 5
電源ケーブル SANWA SUPPLY(サンワサプライ) 電源コード KB-DM2L-2 ケースに充電器を収容するためにL字プラグのもの
USBケーブル SUNGUY type-A & type-c 30cm 2本セット x 2セット(4本) ラズパイ給電用
USBケーブル Anker PowerLine+ Micro USBケーブル 検証ラズパイを載せるのはやめたので、付属のケーブルタイだけ使用
USBケーブル USB 電源ケーブル 変換プラグ付き DC充電コード スイッチングハブ給電用
LANケーブル ミヨシMCO カテゴリ-6スリムLANケ-ブル 15cm x 4本 ハブ~ラズパイ間接続
ケーブル用ベルト 面ファスナー配線ベルト,面ファスナー配線ベルト固定ベルト付き DAISOで調達、結局使わなかった
HDMI変換 ホーリック HDMIマイクロ変換アダプタ 7cm シルバー HDM07-042ADS 初期構築時に使用

キッティング

ラズパイを箱から出してヒートシンクを貼っていきます
IMG_7955.jpg
IMG_7956.jpg
GeekPi クラスタケースに収容していきます。
ラズパイにmicrosdの付属のライザーカードをつけたあとアクリルボードの保護シートを剥がして固定します。
このカードをかませることで分解せずともケース後方からmicrosdの抜き差しが可能になります。(単一障害点が増えるけど)
最初からネジをすべて締めるとなかなか入って行かず基板を破損させてしまいそうになるので、緩めの状態ですべてネジを通してから固定させると付けやすいです。
CPUクーラーにもアクリルボードを取り付けます。
IMG_7957.jpg
サイドパネルに差し込みます。方向が違うと刺さらないようになってますね。※写真では4つラズパイ使ってますが後で1台外しています。
IMG_7958.jpg

全体図

全て取り付けるとこんな感じです。
今回はubuntuを使うのでcanonicalの中の人に貰ったステッカーでも貼っておきます。
IMG_7986.jpg
充電器からの給電なは左から冷却ファン、スイッチングハブ、ラズパイx3 です。
IMG_7985.jpg
IMG_7984.jpg

クラスタの構成はこんな感じ
IMG_7983_キャプション付き.png

構築・ソフトウェア編

OSインストール (全台で実行)

今回はUbntu Server 20.04 LTS(arm64)を使っていきます。

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
#(以下省略)

事前にubuntu desktop 20.04 LTS(amd64)で検証したのですが、k8sの導入でコケました。
また、今後8GBモデルを買った際にmicrosdを差し替えて移行することも考えられるので、raspberry pi OSではなく64bit版があるubuntuにしました。
純正の書き込みツールでmicroSDにOSを書き込んでいきます。microsdはPCに挿しておきます。
Raspberry Pi OS – Raspberry Pi Imager から環境に合ったものをダウンロードします。
※この記事ではWindowsでやっています。
起動すると以下の画面になります。Operating System>[CHOOSE OS]からmicrosdに書き込むOSを選択します。
2020-12-05 (8).png
Ubuntuを選択
2020-12-05 (9).png
Ubuntu Server 20.04.1 LTS(RPi3/4)を選択
2020-12-05 (11).png
OSが選択されましたね。次はSD Card>[CHOOSE SD CARD]からmicrosdを選択します。
2020-12-05 (14).png
挿しておいたmicrosdを選択します。名前はmicrosdの種類やロットで変わると思いますので容量で判断かなあ。
余談ですがpanasonic製microsdフォーマットツールとかだとmicrosd以外のストレージも選択出来てしまうので誤ってフォーマットしてしまうことがありました。
まあ余計なものは取り外ししておいたほうがいいですね。
2020-12-05 (15).png
[WRITE]を選択
2020-12-05 (16).png
中身全部消えるけどええんか?という確認が出ます。確認して[YES]を選択
2020-12-05 (14).png
10分~20分待ちます。microssdの書き込み速度で変わると思います。
2020-12-05 (5).png
書き込みが終わると。「書き込みが終わったんで抜いていいっす」というダイアログが出るので[CONTINUE]を選択して閉じます。
2020-12-05 (17).png
3台分やったら終わりです。

OS初期設定 (全台で実行)

前段作業で書き込んだmicrosdをrasberry piへ差し込み、起動させます。
raspberry piは給電されると自動的に電源が入ります。自分の場合は1台ずつ電源を入れて設定していきました。
設定対象のraspberry piにmicro HDMI端子↔HDMI変換ケーブルを挿してモニタで作業します。(今回は検証用のケーブルを引き回してあるTV使いました。)
ubuntu serverでは初期設定でGUIは無効化、SSHが有効化してあります。ubuntu desktopではgnomeが起動、sshはインストールされていない
コマンドラインが走り、ubuntu login:と出れば起動完了です。
IMG_7972.jpg

初期パスワード変更

ubuntuユーザーの初期パスワードはubuntuで、ログインするとパスワード変更を即されるので変更します。
キーボードが英字設定なので記号などを含めるのは気をつけたほうがいいです。
後で日本語設定入れてからpasswordコマンド使えばいいので、仮でいい気がします。

ipアドレスを確認

$ ip a
#(中省略)
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether XX:XX:XX:XX:XX:ac brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.5/24 brd 192.168.0.255 scope global eth0
#(以下省略)

eth0の結果を確認すればいいので結果が上記の場合192.168.0.5がDHCPで自動的に割あたったIPですね。

★以下の手順以降はPCからSSHクライアント(Teraterm等)から操作します。★

【全ノードで実行】ビルトインアカウント無効化と作業アカウント(きくりんユーザー)作成
## きくりんユーザー作成
$ sudo useradd -m -s /usr/bin/bash kickling  aaaa
## きくりんユーザーパスワード設定
$ sudo passwd kickling
## きくりんユーザーをsudo権限付与
$ sudo adduser kickling sudo
## きくりんユーザーをsudo権限付与確認
$ cat /etc/group | grep sudo
sudo:x:27:ubuntu,kickling
## ビルトイン(ubuntu)をログイン無効化
$sudo usermod -s /usr/sbin/nologin ubuntu
## 無効化(nologin)確認
$ cat /etc/passwd | grep ubuntu
【管理ノードで実行】管理ノードのネットワーク設定

ホスト名設定

## ホスト名設定
$ sudo hostnamectl set-hostname ras-k8s-master1
## ホスト名確認
$ hostname
ras-k8s-master1

ipアドレス(ネットワーク)設定

## ホスト名設定
$ sudo nano /etc/netplan/99-network.yaml
##--ここから--
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.0.71/24
      gateway4: 192.168.0.1
      nameservers:
        addresses:
          - 192.168.0.1
##--ここまでコピーアンドペースト--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## 設定適用
$ sudo netplan apply
##SSHが切断されるので設定したIPでログインし直す
【ワーカーノード1で実行】ワーカーノード1のネットワーク設定

ホスト名設定

## ホスト名設定
$ sudo hostnamectl set-hostname ras-k8s-worker1
## ホスト名確認
$ hostname
ras-k8s-worker1

ipアドレス(ネットワーク)設定

## ホスト名設定
$ sudo nano /etc/netplan/99-network.yaml
##--ここから--
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.0.72/24
      gateway4: 192.168.0.1
      nameservers:
        addresses:
          - 192.168.0.1
##--ここまでコピーアンドペースト--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## 設定適用
$ sudo netplan apply
##SSHが切断されるので設定したIPでログインし直す
【ワーカーノード2で実行】ワーカーノード2のネットワーク設定

ホスト名設定

## ホスト名設定
$ sudo hostnamectl set-hostname ras-k8s-worker2
## ホスト名確認
$ hostname
ras-k8s-worker2

ipアドレス(ネットワーク)設定

## ホスト名設定
$ sudo nano /etc/netplan/99-network.yaml
##--ここから--
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.0.73/24
      gateway4: 192.168.0.1
      nameservers:
        addresses:
          - 192.168.0.1
##--ここまでコピーアンドペースト--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## 設定適用
$ sudo netplan apply
##SSHが切断されるので設定したIPでログインし直す

【全ノードで実行】共通OS設定

一通りネットワーク設定が終わったので接続し直します。

ホスト名 IPアドレス ログインユーザ
ras-k8s-master1 192.168.0.71 kickling
ras-k8s-worker1 192.168.0.72 kickling
ras-k8s-worker2 192.168.0.73 kickling

hostsファイルへ追記

## hosts編集
$ sudo nano /etc/netplan/99-network.yaml
##--ここから--
# Kubernetes Clusters
192.168.0.71       k8s-master1 ras-k8s-master1
192.168.0.72       k8s-worker1 ras-k8s-worker1
192.168.0.73       k8s-worker2 ras-k8s-worker2
##--ここまで下に追記--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## hosts確認
$ cat /etc/hosts

swapの無効化

Docker及びKubernetesの実行時はswapの無効化が必要になるのですが

$ free
              total        used        free      shared  buff/cache   available
Mem:        3884348      945272     1677080        5184     1261996     3001004
Swap:             0           0           0 # ←全て0

最初から無効化されていました。大抵の場合有効になってるはずなのでswap offしてreboot
してあげる必要があると思います。

time zone変更

$ sudo timedatectl set-timezone Asia/Tokyo
$ timedatectl | grep Time
Time zone: Asia/Tokyo (JST, +0900)

key map変更

$ sudo localectl set-keymap jp106
$ localectl
   System Locale: LANG=C.UTF-8
       VC Keymap: jp106
      X11 Layout: jp
       X11 Model: jp106
     X11 Options: terminate:ctrl_alt_bksp

IPv6の無効化(任意)

## sysctl.conf編集
$ sudo nano /etc/sysctl.conf
##--ここから--
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
##--ここまで下に追記--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## カーネルパラメータ反映
$ sudo sysctl -p
## ipv6無効化確認(inet6が表示されないこと)
$ ip a

パッケージの更新

$ sudo apt update
$ sudo apt -y upgrade

cgruop でmemoryの有効化

Ubuntu Server 20.04.1 LTS(RPi3/4)ではデフォルトで無効化されているようです。
↓3列目が0 = enableが無効

$ cat /proc/cgroups | grep memory
memory  0       105     0

/boot/firmware/cmdline.txt に追記します。
1行目に追記します。全一行のファイルなので改行して行追加とかでは無いです。
記法間違えると再起動後に上がってこないかも

## cmdline.txt編集
$ sudo nano /boot/firmware/cmdline.txt
##--ここから--(1文字目はスペース)
 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
##--ここまで1行目の最後に追記--
#ctrl + xで保存、yでファイル名指定(変えなくていい)、Enterで閉じる
## cmdline.txt確認
$ cat /boot/firmware/cmdline.txt
net.ifnames=0 dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
## 設定反映のため、再起動
$ sudo reboot

再起動後、確認
↓3列目が1 = enableが1なので有効になってますね。

$ cat /proc/cgroups | grep memory
memory  x       x     1

iptableがのnftablesバックエンドを参照しないようにする

$ sudo apt-get -y install iptables arptables ebtables
$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
$ sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
update-alternatives: using /usr/sbin/arptables-legacy to provide /usr/sbin/arptables (arptables) in manual mode
$ sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
update-alternatives: using /usr/sbin/ebtables-legacy to provide /usr/sbin/ebtables (ebtables) in manual mode

Dockerのインストール

## 6行まとめてコピーアンドペーストで貼り付けて実行する
$ sudo apt-get -y install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
## リポジトリキー追加
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
## arm64向けDockerリポジトリ追加
$ sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get -y install docker-ce docker-ce-cli containerd.io
## Docker関連バージョン固定設定
$ sudo apt-mark hold docker-ce docker-ce-cli containerd.io
docker-ce set on hold.
docker-ce-cli set on hold.
containerd.io set on hold.

作業ユーザーをDockerグループに追加

$ sudo adduser kicking docker
Adding user `kickling' to group `docker' ...
Adding user kickling to group docker
Done.
$ cat /etc/group | grep docker    # 確認する
docker:x:998:kickling

グループ割当を反映させるため一旦exitで抜けてログインし直します。

Dockerのバージョン確認(任意)

$ docker version
Client: Docker Engine - Community
 Version:           19.03.14
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        5eb3275
 Built:             Tue Dec  1 19:20:49 2020
 OS/Arch:           linux/arm64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.14
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       5eb3275
  Built:            Tue Dec  1 19:19:19 2020
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.3.9
  GitCommit:        ea765aba0d05254012b0b9e595e995c09186427f
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Dockerの動作確認(Hello worldコンテナの起動)
""Hello from Docker!""と表示されればOKです。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world

256ab8fe8778: Pulling fs layer 256ab8fe8778: Downloading     424B/3.367kB256ab8fe8778: Downloading  3.367kB/3.367kB256ab8fe8778: Download complete 256ab8fe8778: Extracting  3.367kB/3.367kB256ab8fe8778: Extracting  3.367kB/3.367kB256ab8fe8778: Pull complete Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323
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/

Kubernetesのインストール

kubeadm、kubectl、kubeletをインストールします。

$ sudo apt-get update && sudo apt-get -y install apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get -y install kubelet kubeadm kubectl
## kubelet kubeadm kubectlのバージョン固定
$ sudo apt-mark hold kubelet kubeadm kubectl
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.

各モジュールのバージョン確認

$ kubeadm version -o json
{
  "clientVersion": {
    "major": "1",
    "minor": "19",
    "gitVersion": "v1.19.4",
    "gitCommit": "d360454c9bcd1634cf4cc52d1867af5491dc9c5f",
    "gitTreeState": "clean",
    "buildDate": "2020-11-11T13:15:05Z",
    "goVersion": "go1.15.2",
    "compiler": "gc",
    "platform": "linux/arm64"
  }
}

$ kubectl version -o json
{
  "clientVersion": {
    "major": "1",
    "minor": "19",
    "gitVersion": "v1.19.4",
    "gitCommit": "d360454c9bcd1634cf4cc52d1867af5491dc9c5f",
    "gitTreeState": "clean",
    "buildDate": "2020-11-11T13:17:17Z",
    "goVersion": "go1.15.2",
    "compiler": "gc",
    "platform": "linux/arm64"
  }
}
The connection to the server localhost:8080 was refused - did you specify the right host or port?

$ kubelet --version
Kubernetes v1.19.4

Kubernetes クラスタの設定

【管理ノードで実行】Master Nodeの初期化

k8sクラスタの初期化作業を実行します。

オプション名 備考
apiserver-advertise-address 192.168.0.71 管理ノードのIP
pod-network-cidr 10.244.0.0/16 ポッド(flannel)のアドレス帯域指定
$ sudo kubeadm init --apiserver-advertise-address=192.168.0.71 --pod-network-cidr=10.244.0.0/16
[sudo] password for kickling: 
W1205 09:15:19.362924    2871 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
[init] Using Kubernetes version: v1.19.4
[preflight] Running pre-flight checks
    [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
    [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local ras-k8s-master1] and IPs [10.96.0.1 192.168.0.71]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost ras-k8s-master1] and IPs [192.168.0.71 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [localhost ras-k8s-master1] and IPs [192.168.0.71 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 35.013277 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.19" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node ras-k8s-master1 as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node ras-k8s-master1 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: g9yaoj.xs04rxh4avt5mikd
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.0.71:6443 --token g9yaoj.xs04rxh4avt5mikd \
    --discovery-token-ca-cert-hash sha256:52488ac2af018a489aae35d55ead8ad784b0593a0db5c8ad79a16505cb30e1d3

最後の2行の
kubeadm join 192.168.0.71:6443 --token g9yaoj.xs04rxh4avt5mikd \
--discovery-token-ca-cert-hash sha256:52488ac2af018a489aae35d55ead8ad784b0593a0db5c8ad79a16505cb30e1d3

Worker Node追加時に必要なコマンドになるのでメモっておきましょう。

トークンが有効なのは24時間なので、過ぎた場合はコマンドで再発行が必要です。
※この時点で後述のkubeadm token list及びkubeadm token create打つとエラーになります
ので次に環境変数設定と入力補完設定を済ませます。

残り時間を確認する場合はkubeadm token listコマンドを実行してTTLの列を確認します。
何も表示されない場合は有効なトークンが無いのでkubeadm token createコマンドで生成します。

【管理ノードで実行】環境変数と入力補完の設定

環境変数設定

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ echo 'KUBECONFIG=$HOME/.kube/config' >> $HOME/.bashrc
$ source $HOME/.bashrc
# kubectlコマンド実行時「The connection to the server ・・・」が表示されないことを確認する
$ kubectl version -o json

入力補完設定

$ source <(kubectl completion bash)
$ echo "source <(kubectl completion bash)" >> $HOME/.bashrc
【管理ノードで実行】Podネットワークアドオンのインストール

Pod間の通信を行うためにflannelを設定します。

$ curl https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml -O
$ kubectl apply -f kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created

flannelの起動を確認します。
kube-flannel-ds-xxxが確認できればOKです。数秒かかりました。

$ kubectl get pods -n kube-system
 NAME                                      READY   STATUS    RESTARTS   AGE
 coredns-f9fd979d6-lhddv                   0/1     Running   0          7m12s
 coredns-f9fd979d6-qxpkl                   1/1     Running   0          7m12s
 etcd-ras-k8s-master1                      1/1     Running   0          7m17s
 kube-apiserver-ras-k8s-master1            1/1     Running   0          7m17s
 kube-controller-manager-ras-k8s-master1   1/1     Running   0          7m17s
 kube-flannel-ds-gr226                     1/1     Running   0          47s
 kube-proxy-r2lsh                          1/1     Running   0          7m12s
 kube-scheduler-ras-k8s-master1            1/1     Running   0          7m17s
【管理ノードで実行】LoadBalancer(MetalLB) のインストール

MetalLBはControllerとSpeakerの2種類のPodで構成されてます。

$ curl https://raw.githubusercontent.com/metallb/metallb/v0.9.4/manifests/namespace.yaml -o namespace.yaml
$ curl https://raw.githubusercontent.com/metallb/metallb/v0.9.4/manifests/metallb.yaml -o metallb.yaml
$ kubectl apply -f namespace.yaml
namespace/metallb-system created
$ kubectl apply -f metallb.yaml
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
secret/memberlist created

MetalLBの起動を確認します。
controller-xxx-xxx と speaker-xxxが確認できればOKです。
controllerはPendingになっていますが、worker nodeを登録後にRunningになります。

$ kubectl get pod -n metallb-system
NAME                         READY   STATUS    RESTARTS   AGE
controller-8687cdc65-s77qq   0/1     Pending   0          55s
speaker-cd548                1/1     Running   0          55s
【ワーカーノード1,ワーカーノード2で実行】Worker nodeをクラスタに追加する

ワーカーノードを登録してクラスタ化します。
"【管理ノードで実行】Master Nodeの初期化"でメモっておいた、
kubeadm join 192.168.0.71:6443 --token g9yaoj.xs04rxh4avt5mikd \
--discovery-token-ca-cert-hash sha256:52488ac2af018a489aae35d55ead8ad784b0593a0db5c8ad79a16505cb30e1d3

の先頭にsudoを付けてワーカーノードで実行します。

$ sudo kubeadm join 192.168.0.71:6443 --token g9yaoj.xs04rxh4avt5mikd \
    --discovery-token-ca-cert-hash sha256:52488ac2af018a489aae35d55ead8ad784b0593a0db5c8ad79a16505cb30e1d3

[sudo] password for kickling: 
[preflight] Running pre-flight checks
    [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
    [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

プロンプトが返ってくるまでしばらく待ちます。

【管理ノードで実行】Worker nodeのクラスタ追加を確認

Master nodeで確認します。
ras-k8s-worker1とras-k8s-worker2がReadyになってれば大丈夫ですね。
また、MetalLBのcontrollerがPendingからRunningになりました。

$ kubectl get nodes
NAME              STATUS   ROLES    AGE    VERSION
ras-k8s-master1   Ready    master   12m    v1.19.4
ras-k8s-worker1   Ready    <none>   100s   v1.19.4
ras-k8s-worker2   Ready    <none>   75s    v1.19.4
$ kubectl get pods -A
NAMESPACE        NAME                                      READY   STATUS    RESTARTS   AGE
kube-system      coredns-f9fd979d6-lhddv                   1/1     Running   0          12m
kube-system      coredns-f9fd979d6-qxpkl                   1/1     Running   0          12m
kube-system      etcd-ras-k8s-master1                      1/1     Running   0          12m
kube-system      kube-apiserver-ras-k8s-master1            1/1     Running   0          12m
kube-system      kube-controller-manager-ras-k8s-master1   1/1     Running   0          12m
kube-system      kube-flannel-ds-bbgrn                     1/1     Running   0          2m2s
kube-system      kube-flannel-ds-gr226                     1/1     Running   0          5m42s
kube-system      kube-flannel-ds-mcfw9                     1/1     Running   0          97s
kube-system      kube-proxy-gsmwm                          1/1     Running   0          97s
kube-system      kube-proxy-p6szf                          1/1     Running   0          2m2s
kube-system      kube-proxy-r2lsh                          1/1     Running   0          12m
kube-system      kube-scheduler-ras-k8s-master1            1/1     Running   0          12m
metallb-system   controller-8687cdc65-s77qq                1/1     Running   0          3m40s
metallb-system   speaker-cd548                             1/1     Running   0          3m40s
metallb-system   speaker-cs6p4                             1/1     Running   0          61s
metallb-system   speaker-z75xl                             1/1     Running   0          46s
【管理ノードで実行】Worker Nodeにラベルを設定する
$ kubectl label node ras-k8s-worker1 node-role.kubernetes.io/worker=worker
node/ras-k8s-worker1 labeled
$ kubectl label node ras-k8s-worker2 node-role.kubernetes.io/worker=worker
node/ras-k8s-worker2 labelednode/ras-k8s-worker2 labeled
#ROLESを確認する
$ kubectl get nodes
NAME              STATUS   ROLES    AGE   VERSION
ras-k8s-master1   Ready    master   21m   v1.19.4
ras-k8s-worker1   Ready    worker   11m   v1.19.4
ras-k8s-worker2   Ready    worker   11m   v1.19.4

Kubernetes で コンテナを動かす

Docker が動作しているホストのHostnameを表示するNginxコンテナ - Qiita
を参考に、どのノードと接続しているのか確認していきます。
yamlをエディタで作成します。

display-hostname.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: nginx-prod
---
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: pool-ips  # MetallbのIPプール名
      protocol: layer2
      addresses:
      - 192.168.0.211-192.168.0.215
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-lb # Service(LoadBalancer) の名前
  namespace: nginx-prod
  annotations:
    metallb.universe.tf/address-pool: pool-ips # MetallbのIPプール名
spec:
  type: LoadBalancer
  ports:
    - name: nginx-service-lb
      protocol: TCP
      port: 8080 # ServiceのIPでlistenするポート
      nodePort: 30080 # nodeのIPでlistenするポート(30000-32767)
      targetPort: 80 # 転送先(コンテナ)でlistenしているPort番号のポート
  selector: # service のselctorは、matchLabels 扱いになる
    app: nginx-pod # 転送先の Pod のラベル
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment # Deployment の名前(ReplicaSetの名前もこれになる)
  namespace: nginx-prod
spec:
  selector:
    matchLabels: # ラベルがマッチしたPodを対象とするReplicaSetの作成
      app: nginx-pod
  replicas: 2
  template: # Pod のテンプレート
    metadata:
      name: nginx-pod # Pod の名前
      namespace: nginx-prod
      labels: # Pod のラベル
        app: nginx-pod
    spec:
      containers: # コンテナの設定
        - name: nginx-container # コンテナの名前
          image: yasthon/nginx-display-hostname # イメージの名前
          env:
            - name: nginx-container
          ports:
            - containerPort: 80 # コンテナのポート
          volumeMounts:
            - name: file-hostname
              mountPath: /usr/share/nginx/html/hostname
      volumes:
        - name: file-hostname
          hostPath:
            path: /etc/hostname

リソースを作成します。
Pod、Service、Deployment、ReplicaSet、ConfigMapの作成を確認します。
ServiceのEXTERNAL-IPは、IPプールから割り当たっています。

$ kubectl apply -f display-hostname.yaml
namespace/nginx-prod created
configmap/config created
service/nginx-service-lb created
deployment.apps/nginx-deployment created
kickling@ras-k8s-master1:~$ kubectl get all -n nginx-prod
NAME                                    READY   STATUS              RESTARTS   AGE
pod/nginx-deployment-7ff4cc65cd-nvchj   0/1     ContainerCreating   0          19s
pod/nginx-deployment-7ff4cc65cd-zlwnv   0/1     ContainerCreating   0          19s

NAME                       TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)          AGE
service/nginx-service-lb   LoadBalancer   10.102.181.109   192.168.100.211   8080:30080/TCP   19s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   0/2     2            0           19s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-7ff4cc65cd   2         2         0       19s
#確認する
$ kubectl get configmap -n metallb-system
NAME     DATA   AGE
config   1      47s

Serviceの詳細情報から、LoadBalancer Ingress とPortの値を確認します。

$ kubectl describe svc nginx-service-lb -n nginx-prod
Name:                     nginx-service-lb
Namespace:                nginx-prod
Labels:                   <none>
Annotations:              metallb.universe.tf/address-pool: pool-ips
Selector:                 app=nginx-pod
Type:                     LoadBalancer
IP:                       10.102.181.109
LoadBalancer Ingress:     192.168.100.211
Port:                     nginx-service-lb  8080/TCP
TargetPort:               80/TCP
NodePort:                 nginx-service-lb  30080/TCP
Endpoints:                10.244.1.3:80,10.244.2.2:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason        Age                From                Message
  ----    ------        ----               ----                -------
  Normal  IPAllocated   58s                metallb-controller  Assigned IP "192.168.100.211"
  Normal  nodeAssigned  42s                metallb-speaker     announcing from node "ras-k8s-worker1"
  Normal  nodeAssigned  32s                metallb-speaker     announcing from node "ras-k8s-worker2"

接続確認

LoadBalancer IngressのIPアドレスとPortのポート番号へcurlして接続確認します。

$ curl 192.168.0.211:8080/index.sh
<html><head>
<title>ras-k8s-worker2</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : ras-k8s-worker1
</body></html>

何度か繰り返すと接続先がras-k8s-worker2になったりras-k8s-worker1に戻ったりして
LBで振り分けされていることがわかります。

抜線確認

ワーカーノード2のLANケーブルを抜きます。
ras-k8s-worker2の振り分けは停止して、ras-k8s-worker1へ接続のみになることが確認できます。

$ curl 192.168.0.211:8080/index.sh
<html><head>
<title>ras-k8s-worker2</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : ras-k8s-worker1
</body></html>
$ curl 192.168.0.211:8080/index.sh
<html><head>
<title>ras-k8s-worker2</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head><body>
HOSTNAME : ras-k8s-worker1
</body></html>

##ノード一覧を確認するとras-k8s-worker2はNotReadyになっています
$ kubectl get nodes
NAME              STATUS     ROLES    AGE   VERSION
ras-k8s-master1   Ready      master   44m   v1.19.4
ras-k8s-worker1   Ready      worker   34m   v1.19.4
ras-k8s-worker2   NotReady   worker   34m   v1.19.4

結線し直すと戻ります。

ブラウザからは
http://192.168.0.211:8080/index.shで確認できます。

所感

後述する先駆者様の写経がベースとなりましたが、環境差分やマークダウン記法が初めてなこともあり
結構書き直しの部分が大半で勉強になりました。
また、Kubernetesの構築は何度も挑戦するもkubeletの初期化エラーにぶつかり挫折しかけていたので
今回ついにKubernetesの実機環境ができあがって非常に嬉しいです。
今はなんとなくよくわからんがなんか出来た状態なので少しづつ検証して仕組みを勉強したいと思います。

以上、k8sクラスタ構築とwebサーバの動確まで でした。

参考

kubeadmを使ってクラスターを構築する | Kubernetes
RaspberryPi 4 にUbuntu20.04 をインストールして、Kubernetes を構築してコンテナを動かす - Qiita
3台のRaspberry Pi 4でKubernetesクラスターを構築する(Ubuntu Server 20.04 LTS 64bit) | kimama.cloud
Docker が動作しているホストのHostnameを表示するNginxコンテナ - Qiita
MetalLB, bare metal load-balancer for Kubernetes
KubernetesロードバランサーのMetalLBを導入した話(Necoプロジェクト体験入部) - Cybozu Inside Out | サイボウズエンジニアのブログ
Markdown記法 サンプル集 - Qiita
Raspberry Piでkubernetesクラスタを組む | SIOSDX | コンテナの窓口

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

簡単にDockerでreact環境を速攻で作る

こんにちは、IT技術者の田中です。
簡単にDockerでreact環境を速攻で作る

背景

Dockerでreact環境を速攻で作る最新(2020年12月12日)情報がなく困ったので、記事を書きました!
(色々惜しい記事はありましたが、ちょこちょこミスがあり困りました)

前提としてはmacbook proで構築しました!

フォルダ構成

下記のフォルダ構成で作ります。

react
├ Dockerfile
├ docker-compose.yml
└ react-sample/

Dockerfile

Dockerfileの内容

FROM node:15.3.0-alpine
WORKDIR /usr/src/app

docker-compose.yml

docker-compose.ymlの内容

version: '3'
services:
  node:
    build: 
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./:/usr/src/app
    command: sh -c "cd react-sample && yarn start"
    ports:
      - "3000:3000"

dockerビルドを実行する

docker-compose.ymlのフォルダに移動します。

> docker-compose build

上記が完了するまで待ちます。

docker-composeでreactをプロジェクト作成

下記のコマンドで、reactのプロジェクトを作成する。

> docker-compose run --rm node sh -c "npm install -g create-react-app && create-react-app react-sample"

dockerのコンテナを起動する

下記のコマンドでdockerコンテナを起動する。

docker-compose up

reactの起動を確認する

ブラウザーで下記のURLにアクセスする
http://localhost:3000

下記の画面が表示されたら無事成功
スクリーンショット 2020-12-12 午後5.16.28.png

終わり!

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

docker composeで指定する要素メモ

・要素
1--version:docker-composeのバージョン
2--立ち上げるコンテナの名前
3--image:立ち上げるコンテナのイメージ
4--ports:ポート番号
5--networks:どのネットワークに接続させるか
6--volumes:どこのvolumeにつないでデータを保存するか
7--environment:環境変数

・具体例
version:'3.7'
services:
nginx:
image:nginx:'バージョン数'
ports:
-8080:80
environment:

・contextで、docker-compose.ymlからみてdockerfileがどこにあるのかを指定して

・build
docker fileがおいてあディレクトリや、docker fileの名称を指定していると思っておけば良い?

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

Unsupported config option for services service: ‘app’ エラー

はじめに

docker-compose.yml を書き換えた時にエラーが発生したので解決方法を備忘録として残す。

エラー

Unsupported config option for services service: ‘app’

解決方法

docker-compose が古いらしい

最新のバージョンに変更する
最新のバージョンを見るには こちらから

curl -L “https://github.com/docker/compose/releases/download/バージョン指定/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose

完了

参考記事

https://qiita.com/takepan/items/172302ecb1ead91c0cab

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

AWS Lambdaがコンテナイメージ対応したらしいのでLambda上でJUMAN++を動かしてみた

無計画なAdvent Calendar駆動開発は時間が足らなくてつらい。natsuumeです。

この記事はOpt Technologies AdventCalendarの12日目の記事です。
11日は@shoyaokayamaさんで「LINEThingsとM5CoreInkを使ってO2Oマーケティングを体験してみる」でした。

はじめに

AWS Re:InventにてAWS Lambdaがコンテナイメージサポートという発表がありました。
しかも最大10GBまでのコンテナイメージが可能だそうです。

となればとりあえず利用方法として下記のような例が思いつきます。

  • AWS Lambda上で形態素解析器を動かす
  • AWS Lambda上で深層学習の推論を動かす

今回は深層学習の推論エンドポイントとして使う方は時間の都合上試していませんが、こちらも後でやってみたいところです。

趣味で深層学習試しても推論エンドポイントずっと立てて公開するのは金銭的にちょっと……という感じだったので、多少実行時間かかるとしてもサーバレスで推論エンドポイント立てられるのは夢がありますね。

深層学習モデルをLambdaに乗せる参考例

また、Lambda上で形態素解析器等を手軽に動かせるようになれば、S3に溜まっていくテキストデータを加工・整形して蓄積するというフローも楽に作れるようになりそうです。

というわけで、今回はAWS Lambda上で形態素解析器(今回はJUMAN++)を動かすのを試していきます。

なお先にネタバレすると当初はKNPも入れる予定でしたが、KNPを入れたらdocker imageのサイズが10GBを超過したため諦めました。

全体構成

今回は最小構成でLambdaにテキストを投げたら解析結果をそのまま返すLambdaを作ります。

使用した全コードはgithubに下記公開してあります。
https://github.com/Natsuume/lambda-container-sample

cdk

cdk.ts
import { DockerImageCode, DockerImageFunction } from "@aws-cdk/aws-lambda";
import * as cdk from "@aws-cdk/core";
import { Duration } from "@aws-cdk/core";

export class LambdaContainerSampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const lambda = new DockerImageFunction(this, "jumanpp-lambda", {
      code: DockerImageCode.fromImageAsset("./docker", {
        cmd: ["/var/task/jumanpp-lambda.jumanppHandler"],
        entrypoint: ["/lambda-entrypoint.sh"],
      }),
      timeout: Duration.seconds(30),
    });
  }
}

DockerImageFunctionはイメージのpush先がaws-cdk/assets固定ですが、このような記述だけでローカルのdockerfileをpushしてlambda上で動かせます。
簡単。

また、後述しますがタイムアウトは長めに取っておきます。

Dockerfile

詳しくは知らないのですがAmazon Linux2はCentOS系らしいです。
つまりaptが使えない。つらい。

yumでinstallできるcmakeのバージョンは2.x系ですがJUMAN++のインストールには3.0以上のcmakeが必要だったり、という関係で色々インストールしたりしています。

ubuntu系ならもっと簡単にinstallできた記憶があるのでそっちベースで書いたほうが楽という可能性も無きにしもあらずですが、その辺は詳しくないので今回は公式で提供されているpublic.ecr.aws/lambda/nodejs:12を使っています。

FROM public.ecr.aws/lambda/nodejs:12

ENV JUMAN_VERSION 2.0.0-rc3
# ENV KNP_VERSION knp-4.20

RUN yum update -y
RUN yum upgrade -y
RUN yum -y groupinstall "Development Tools"
RUN yum install -y wget

# wget cmake
RUN wget https://cmake.org/files/v3.18/cmake-3.18.0.tar.gz
RUN tar -xvzf cmake-3.18.0.tar.gz
RUN rm cmake-3.18.0.tar.gz

# wget jumanpp
RUN wget https://github.com/ku-nlp/jumanpp/releases/download/v${JUMAN_VERSION}/jumanpp-${JUMAN_VERSION}.tar.xz
RUN tar xvf jumanpp-${JUMAN_VERSION}.tar.xz
RUN rm jumanpp-${JUMAN_VERSION}.tar.xz

# wget knp
# RUN wget http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/knp/${KNP_VERSION}.tar.bz2
# RUN tar jxvf ${KNP_VERSION}.tar.bz2
# RUN rm ${KNP_VERSION}.tar.bz2

# install cmake
RUN yum -y install openssl-devel
RUN cd cmake-3.18.0 && \
    ./bootstrap && \
    make && \
    make install

# install juman++v2
RUN cd jumanpp-${JUMAN_VERSION} && \
    mkdir build && \
    cd build && \
    cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local && \
    make && \
    make install

# install knp
# RUN cd ${KNP_VERSION} && \
#     ./configure && \
#     make && \
#     make install

# clean
RUN cd cmake-3.18.0 && \
    make uninstall
RUN rm -rf cmake-3.18.0
RUN yum -y groupremove "Development Tools"
RUN yum remove -y wget

COPY ./jumanpp-lambda.js /var/task
CMD ["/var/task/jumanpp-lambda.jumanppHandler"]
ENTRYPOINT ["/lambda-entrypoint.sh"]

EXPOSE 8080

lambda

lambda.ts
import * as childProcess from "child_process";
import * as util from "util";

exports.jumanppHandler = async (event: { content: string }) => {
  const out = await util
    .promisify(childProcess.exec)(`echo "${event.content}" | jumanpp`)
    .then((result) => result.stdout)
    .then((output) => output.split("\n"))
    .then((output) => output.filter((s) => s.length > 0));

  return {
    result: out,
  };
};

今回はLambda上でJUMAN++が動くかの確認がメインのため非常に簡素なコードで済ませています。
実際に運用する際には、

  • execを使用しているので危険な入力が与えられる可能性があるのであればその対策
  • JUMAN++の解析が失敗するような入力を正規化する(参考:稀によくあるKNPのはまりどころ

などの入力に対する多少の変形が必要です。

結果

無事に入力文字列に対して解析結果を返すLambdaが完成しました。
image.png

前述のタイムアウトと関連しますが、実行時間は初回のみ多少時間がかかります。

実行回数 実行時間(ms)
1回目 11547
2回目 395

2回目以降はよろしくやってくれるのか、問題ない速度で動きます。

ハマったポイント

entrypoint

cdkのDockerImageFunctionの設定でかなり時間を費やしました。
DockerImageCode.fromImageAssetが引数にとるAssetImageCodePropsのentrypointの挙動が個人的にはかなりわかりにくかったです。

AssetImageCodePropsは下記のプロパティを持ちます。

buildArgs?: { [string]: string },
cmd?: string[],
entrypoint?: string[],
file?: string,
target?: string,

このentrypointについて、公式リファレンスでは

Specify or override the ENTRYPOINT on the specified Docker image or Dockerfile.

と記載されています。
(参考: https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.AssetImageCodeProps.html)

entrypointがoptionalだったためDockerfile側でENTRYPOINTを指定すれば問題ないと思ったのですが、どうもcdk側でもentrypointを指定しないとdeploy時にentrypointが空になるようでした。

Dockerfileの置き場

DockerImageCode.fromImageAssetでDockerfileをプロジェクト直下においてディレクトリを./に指定するとcdkコマンドで死にます。
(参考:https://github.com/aws/aws-cdk/issues/3899)

ダメなパターン.ts
    const lambda = new DockerImageFunction(this, "jumanpp-lambda", {
      code: DockerImageCode.fromImageAsset("./", { // "./"指定は死ぬ
        cmd: ["/var/task/jumanpp-lambda.jumanppHandler"],
        entrypoint: ["/lambda-entrypoint.sh"],
      }),
      timeout: Duration.seconds(30),
    });

今回はdockerfile用にディレクトリを掘って回避しました。

おわりに

Lambdaでコンテナイメージが動かせるようになって、容量に限りはあるもののこのように実行ファイルもLambdaで手軽に使えるようになりました。
これによって手軽にサーバレスでできることがぐっと広がったのではないかと思います。

以上です。

なお、アドベントカレンダーの13日も私予定ですが、間違いなく遅刻します。

参考文献

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

S3互換のオブジェクトストレージMinIOを利用したローカル開発環境のサンプル

こちらはニフティグループアドベントカレンダーの13日目記事です。
昨日は @esu_eichiさんの flutter で UI をどう作るかの基礎でしたね!
僕は、ほぼバックエンドしかやってこなかったのでUIの勉強もしないとなあ

はじめに

みなさんはS3を利用したアプリケーションを作成する際に開発環境をどのように構築していますか?
endpointにawsのs3を指定するのは楽ですが少額とはいえお金がかかるので、ローカルほうが検証しやすいですよね。
今回の記事では、ローカルS3の開発環境のサンプルを作ってみました!

サンプルでできること

pythonのコンテナからminio(s3)に対してAWS SDKでアクセスし、以下の事ができます

  • バケット作成と削除
  • ファイル作成と削除と内容取得

環境

  • Microsoft Windows [Version 10.0.18363.1198]
  • Docker version 19.03.13, build 4484c46d9d
  • docker-compose version 1.27.4, build 40524192

ファイル構成

│─docker-compose.yml
│─Pipfile
│─python.Dockerfile
└─sample
    └─create_bucket.py
    └─create_file.py
    └─read_file.py
    └─delete_file.py
    └─delete_bucket.py

ファイル内容

docker-compose.yml

各サービスの説明:

  • python
  • minio
    • 使用しているコンテナイメージ: https://hub.docker.com/r/minio/minio/
    • ports
      • minioのデフォルトポートは9000です。僕の環境だと9000番ポートは競合するため、ホストに9090を割り当てています。
    • environment
      • minioにアクセスする際のキーを環境変数として指定しています
  • createbuckets
    • 使用しているコンテナイメージ: https://hub.docker.com/r/minio/mc/
    • entrypoint
      • until ~~ の部分では、minioに接続できるまで待機しています。ここでは、minioにアクセスする際のキーを利用し、接続しています。
      • 接続できしだい、default-bucketの作成を行っています。
docker-compose.yml
version: "3.7"
services:
  python:
    image: sample/python
    container_name: python
    build:
      context: ./
      dockerfile: python.Dockerfile
    stdin_open: true
    volumes:
      - ./sample:/sample
    networks:
      default:
        ipv4_address: 172.21.1.2
  minio:
    container_name: minio
    image: minio/minio:latest
    volumes:
      - ./minio:/export
    ports:
      - "9090:9000" 
    environment:
      - MINIO_ACCESS_KEY=miniominio
      - MINIO_SECRET_KEY=miniominio
    networks:
      default:
        ipv4_address: 172.21.1.3
    command: server /export
  createbuckets:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      until (/usr/bin/mc config host add minio http://minio:9000 miniominio miniominio) do echo '...waiting...' && sleep 1; done;
      /usr/bin/mc mb minio/default-bucket;
      /usr/bin/mc policy download minio/default-bucket;
      exit 0;
      "
networks:
  default:
    ipam:
      driver: default
      config:
        - subnet: "172.21.1.0/24"

Pipfile

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
boto3 = "==1.12.9"

[dev-packages]

[requires]
python_version = "3.8"

python.Dockerfile

python.Dockerfile
FROM python:3.8-slim

# 日本時間対応
ENV TZ=Asia/Tokyo

# 作業ディレクトリ
ENV WORKDIR /sample

# ディレクトリ作成
RUN mkdir ${WORKDIR}
WORKDIR ${WORKDIR}
COPY . ${WORKDIR}

# Python依存ライブラリをインストール
RUN pip install --upgrade pip && \
    pip install pipenv && \
    pipenv install

create_bucket.py

boto3.resourceでは、minioの環境変数で決めたMINIO_ACCESS_KEY、MINIO_SECRET_KEYを指定しています。

create_bucket.py
import boto3

# minioをendpointにする
s3_resource = boto3.resource(
    's3',
    endpoint_url='http://minio:9000',
    aws_access_key_id='miniominio',
    aws_secret_access_key='miniominio'
)

# bucket名
bucket_name = "test-bucket"

# bucketを作成
s3_resource.create_bucket(Bucket=bucket_name)

create_file.py

create_file.py
import boto3

# minioをendpointにする
s3_resource = boto3.resource(
    's3',
    endpoint_url='http://minio:9000',
    aws_access_key_id='miniominio',
    aws_secret_access_key='miniominio'
)

# bucket名
bucket_name = "test-bucket"
# file名
file_name = "test-file"

# bucketにファイル作成
s3_resource.Object(bucket_name, file_name).put(Body="hogehoge")

read_file.py

read_file.py
import boto3

# minioをendpointにする
s3_resource = boto3.resource(
    's3',
    endpoint_url='http://minio:9000',
    aws_access_key_id='miniominio',
    aws_secret_access_key='miniominio'
)

# bucket名
bucket_name = "test-bucket"
# file名
file_name = "test-file"

# ファイルの内容を読み込む
file_data = s3_resource.Object(bucket_name, file_name).get()[
    'Body'].read().decode('utf-8')
print("#test-fileのファイル内容の出力")
print(file_data)

delete_file.py

delete_file.py
import boto3

# minioをendpointにする
s3_resource = boto3.resource(
    's3',
    endpoint_url='http://minio:9000',
    aws_access_key_id='miniominio',
    aws_secret_access_key='miniominio'
)

# bucket名
bucket_name = "test-bucket"
# file名
file_name = "test-file"

# test-fileを削除
s3_resource.Object(bucket_name, file_name).delete()

delete_bucket.py

delete_bucket.py
import boto3

# minioをendpointにする
s3_resource = boto3.resource(
    's3',
    endpoint_url='http://minio:9000',
    aws_access_key_id='miniominio',
    aws_secret_access_key='miniominio'
)

# bucket名
bucket_name = "test-bucket"

# bucketを削除
s3_resource.Bucket(bucket_name).delete()

動作確認

docker-compose.ymlがあるディレクトリで以下コマンドを打ちます

$ docker-compose build
$ docker-compose up -d

コンテナが起動したら以下を確認します。

#docker-compose.yml より抜粋
    environment:
      - MINIO_ACCESS_KEY=miniominio
      - MINIO_SECRET_KEY=miniominio

image.png

ログインすると以下のような画面が表示されるはずです。
その際にdefault-bucketが作成されていることを確認しましょう。
これはdocker-compose.ymlのcreatebucketsで作成されたバケットになります。
image.png

画面確認ができたので、次はpythonコンテナに入り、サンプルソースを実行していきます。

pythonコンテナに入りpipenv shellをする

$ docker exec -it python bash
root@35d6e218dd79:/sample# pipenv shell
Creating a Pipfile for this project...
Launching subshell in virtual environment...
root@35d6e218dd79:/sample#  . /root/.local/share/virtualenvs/sample-Q5cUdPp_/bin/activate

create_bucket.pyを実行しminioのweb画面からtest-bucketが作成されていることを確認する

(sample) root@35d6e218dd79:/sample# python create_bucket.py 
(sample) root@35d6e218dd79:/sample# 

http://localhost:9090/minio/ 
にアクセスし、以下のようにtest-bucketが作成されていることを確認する

image.png

create_file.pyを実行しminioのweb画面でtest-fileが作成されていることを確認する

(sample) root@35d6e218dd79:/sample# python create_file.py
(sample) root@35d6e218dd79:/sample# 

http://localhost:9090/minio/test-bucket/
にアクセスし、以下のようにtest-fileが作成されていることを確認する

image.png

read_file.pyを実行しtest-fileの中身を読み込めることを確認する

(sample) root@35d6e218dd79:/sample# python read_file.py
#test-fileのファイル内容の出力
hogehoge

delete_file.pyを実行し、minioのweb画面でtest-fileが削除されていることを確認する

(sample) root@35d6e218dd79:/sample# python delete_file.py 
(sample) root@35d6e218dd79:/sample# 

http://localhost:9090/minio/test-bucket/
にアクセスし、以下のようにtest-fileが削除されていることを確認する

image.png

delete_bucket.pyを実行し、minioのweb画面でtest-bucketが削除されていることを確認する

(sample) root@35d6e218dd79:/sample# python delete_bucket.py 
(sample) root@35d6e218dd79:/sample# 

http://localhost:9090/minio/
にアクセスし以下のようにtest-bucketが削除されていることを確認する
image.png

無事サンプルの実行ができました!

おわりに

今回はMinIOを利用したローカル開発環境のサンプルを作成しました。
qiita投稿は初めてなのでお手柔らかにお願いします(?)

明日は@kanishionoriさんです~~

参考サイト

今回の記事を作成するにあたり参考にした記事になります。大変お世話になりました。

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

AWSにコンテナ環境を構築する

はじめに

コンテナ開発環境をAWS上に構築します。
なお、本記事は以下のUdemy講座を元にした覚書です。
米国AI開発者がゼロから教えるDocker講座

準備

  1. コンソールよりAWSのEC2インスタンスを生成
    https://aws.amazon.com/jp/console/

  2. OSはUbuntuなどを選択

  3. インスタンスが起動したらログインし、Dockerをインストール

# sshでAWSにログイン
ssh -i /Users/yukokanai/work/aws/ssh/mydocker.pem ubuntu@ec2-13-230-103-136.ap-northeast-1.compute.amazonaws.com

# dockerインストール
sudo apt-get update
sudo apt-get install docker.io

# dockerグループを作ってubuntu(ユーザー)を入れる(sudoなしでdockerを使える様にする)
sudo gpasswd -a ubuntu docker

# 一度ログアウトしてからもう一度入り直すと使える様になる
exit

AWSのインスタンスにimageをアップしコンテナ起動

いくつかあります。制約などに応じて使い分けてください。

手段①Docker HubなどのレジストリにDocker imageをpullする

インスタンスがネットにつなげられない場合は使えないです。

1.Docker Hubにアカウントを作成
https://hub.docker.com/

2.ローカル環境にDockerをインストール

3.ローカル環境にてimageファイルを作成

docker build -f Dockerfile . {image名(repository)}:{tagname}

4.ローカル環境よりDockerHubにpush

docker push {アカウント名}/{image名(repository)}:{tagname}

5.AWSにログインし、DockerHubよりpull

docker pull {アカウント名}/{image名(repository)}:{tagname}

6.DLされたimageファイルよりコンテナ起動

docker run -it {imageID}

手段②Dockerfileを送る

ビルドコンテキストの差異が出る可能性がある
Cloudがネットにつなげられない場合は使えない

1.ローカル環境にDockerをインストール

2.ローカル環境にてDockerfileを作成

3.sftpにてAWSへファイル転送(インスタンスは起動しておく)

# sftpでAWSへログイン
sftp -i /Users/yukokanai/work/aws/ssh/mydocker.pem ubuntu@ec2-13-230-103-136.ap-northeast-1.compute.amazonaws.com

# 圧縮ファイルをAWSインスタンスへput
put Dockerfile /home/ubuntu

# ログアウト
exit

4.AWSにログインし、imageファイルを作成

# ビルド
docker build -f Dockerfile . {image名}:{tagname}

# imageファイルが作成されていることを確認
docker images

5.作成されたimageファイルよりコンテナ起動

docker run -it {imageID}

手段③Docker imageを圧縮して送る

他手段に比べてimageファイルはサイズが大きいので時間がかかる

1.ローカル環境にDockerをインストール

2.ローカル環境にてimageファイルを作成

docker build -f Dockerfile . {image名}:{tagname}

3.imageファイルを圧縮

docker save {imageID} > myimage.tar

4.sftpにてAWSへファイル転送(インスタンスは起動しておく)

# sftpでAWSへログイン
sftp -i /Users/yukokanai/work/aws/ssh/mydocker.pem ubuntu@ec2-13-230-103-136.ap-northeast-1.compute.amazonaws.com

# 圧縮ファイルをAWSインスタンスへput
put myimage.tar /home/ubuntu

# ログアウト
exit

5.AWSにログインし、圧縮ファイルを展開してimageファイルを作成

# 圧縮ファイルを展開
docker load < myimage.tar

# imageファイルが作成されていることを確認
docker images

6.作成されたimageファイルよりコンテナ起動

docker run -it {imageID}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Movable Type Serverless - LambdaコンテナでMTを動かしてみた

Movable Type Advent Calendar 2020 12日目です。

つい先日、AWS LambdaがDockerコンテナをサポートしました。

これはアツい!ということでMTを(途中まで)動かしてみた、という話です。

Movable Type Serverless

最終的にはこんなイメージでいます。

mt-serverless-arch2.png

MTの静的出力という特徴を活かし、Webサーバーだけ常時起動させますが、MTは普段存在すらしておらず、編集者が使うときだけ起動します。

  • 夜中や休日など編集者が使わない時間はコンピューティングコスト節約!
  • 利用者が増えたらRDSやEFSの限界まで事実上無限に性能拡張!
  • もし再構築を並列で実行できたら時間を圧倒的に短縮!

なにこれすごい!

とりあえずログインできるところまでやってみた

今回はRDSと連携してログインできるところまでやってみました。

mt-serverless-trial2.png

  • EFSとの連携以降はやってません。
  • セキュリティ考慮していません。
  • 日本語だと文字化けしたのでとりあえず英語で動かします。

とまあ、PoCレベルなので誰か遺志を継いでください。

手順1 RDSインスタンスを起動

RDSにMT用のMySQLデータベースインスタンスとユーザーを作成します。

一時的な実験なのでセキュリティは考慮していません! 随時読み替えて適切に設定してください。

create user 'mt_serverless'@'%' identified by '(データベースのパスワード)';
grant all on mt.* to 'mt_serverless'@'%' with grant option;
flush privileges;

手順2 MTのプログラム一式を展開する

ここからはRDSに接続可能な環境で作業します。

開発者ライセンスでMTを入手します。

https://www.sixapart.jp/inquiry/movabletype/developer.html

手順3 mt-config.cgi

MTを展開したディレクトリ直下に設定ファイルmt-config.cgiを作成します。

※ 日本語が文字化けしちゃったので英語UIに逃げます。

mt-config.cgi
CGIPath        /mt/
StaticWebPath  /mt/mt-static/
StaticFilePath /app/mt-static

ObjectDriver DBI::mysql
Database mt
DBUser mt_serverless
DBPassword (データベースのパスワード)
DBHost (RDSインスタンスのホスト名)

EmailAddressMain (管理者メールアドレス)

DefaultLanguage en

ImageDriver Imager

手順4 Dockerfile

ひとまずplack/PSGIで動かすため、MTを展開したディレクトリ直下にこんなDockerfileを作ります。

個人的に慣れてるUbuntuを使っていますが、ディストリビューションはお好きにどうぞ。

Dockerfile
FROM ubuntu:20.04

RUN apt-get update \
  && apt-get install -y perl cpanminus build-essential \
    libdbi-perl libdbd-mysql-perl \
    libimager-perl libxmlrpc-lite-perl \
    libplack-perl libcgi-psgi-perl \
  && cpanm XMLRPC::Transport::HTTP::Plack

RUN cpanm AWS::Lambda

RUN apt-get -y remove build-essential \
  && apt-get -y clean \
  && apt-get -y autoremove \
  && rm -rf /var/lib/apt/lists/* /var/cache/apt/*

ADD . /app
WORKDIR /app

CMD [ "plackup", "mt.psgi" ]

ここでインストールしているナイスなモジュールAWS::Lambdaが、Lambda - Dockerコンテナ - PSGIアプリケーションとしてのMTを繋いでくれます。

https://github.com/shogo82148/p5-aws-lambda

手順5 PSGI版の動作確認

docker build -t mt-serverless .
docker run -it --rm -p 5000:5000 mt-serverless

ブラウザから、http://localhost:5000/mt/mt.cgi (localhostは作業環境に合わせて)にアクセスすると、セットアップウィザードが開始されます。

Create_Your_Account___Movable_Type_Pro.png

文字化けの問題で、LanguageEnglishでひとまず進めてください。

Initializing_database______Movable_Type_Pro.png

データベースのセットアップができたらOKです。

コンテナサポート ≒ カスタムランタイム

AWS Lambdaのコンテナサポートは、2年前にリリースされたカスタムランタイムのプロトコルを踏襲しています。

カスタムランタイムはHTTPによりLambdaサービスと連携しますが、コンテナも同様です。なのでコンテナの情報はまだ少ないのですが、カスタムランタイムについて調べると大抵の問題は解決できます。

今回も、AWS Lambdaカスタムランタイム用のモジュールAWS::Lambdaを大いに活用します。

https://github.com/shogo82148/p5-aws-lambda

手順6 コンテナ用エントリーポイント bootstrap

MTのディレクトリ直下にbootstrapを作成し、実行権限を付与します。

boostrap
#!/usr/bin/perl

use strict;
use AWS::Lambda::Bootstrap;
use File::Basename;

# Lambdaサービスから渡されそうだが…
$ENV{'LAMBDA_TASK_ROOT'} = dirname(__FILE__);

# Lambdaにハンドラの入力欄がないからこちらは来ないかも?
# AWS::Lambda向けに固定
$ENV{'_HANDLER'} = 'mt-lambda.handler';

my $bootstrap = AWS::Lambda::Bootstrap->new;
$bootstrap->handle_events;

カスタムランタイムと違い、名前はbootstrapでなくてもよいのですが慣習に沿います。

chmod +x bootstrap

手順7 Lambdaコンテナ用MTエントリーポイント mt-lambda.pl

次にbootstrapから呼び出されるMTのエントリーポイントmt-lambda.plを作成します。

mt-lambda.pl
use strict;
use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/lib"    : 'lib';
use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/extlib" : 'extlib';
use MT::PSGI;
use AWS::Lambda::PSGI;
my $app = MT::PSGI->new()->to_app();
my $func = AWS::Lambda::PSGI->wrap($app);

sub handler {
  my $payload = shift;
  return $func->($payload);
}

1;

このファイル名とsub handlerの名称は、下記のようにbootstrapの記述と関連しているので変更する場合は併せて行う必要があります。

$ENV{'_HANDLER'} = 'mt-lambda.handler';
# mt-lambda → mt-lambda.pl
# handler → sub handler

最初はbootstrapファイル内ですべて完結させる予定でしたが、AWS::Lambdaモジュールの実装によりそれが難しかったのでbootstrapmt-lambda.plに分かれています。

手順8 ECRにプッシュ

ECR上のリポジトリにイメージをプッシュします。細かい説明は省略します。

docker tag mt-serverless (固有のID).dkr.ecr.ap-northeast-1.amazonaws.com/mt-serverless:latest
docker push (固有のID).dkr.ecr.ap-northeast-1.amazonaws.com/mt-serverless:latest

手順9 Lambdaを作成

コンテナイメージを元にLambda関数を作成します。

Lambda.png

今のコンテナイメージはplackupでPSGI版のMTを起動するので、CMDにLabmda用のエントリポイント/app/bootstrapを指定します。

作成後にLambdaのメモリとタイムアウトを変更します。

適切な設定は要検討ですが、とりあえずタイムアウト3秒は短いので30秒にしました。

Lambda.png

手順10 Elastic Load Balancerの作成

やっと終わりが見えてきました…

Application Load Balancerを作成してLambda関数を割り当てます。

  • Application Load Balancer (HTTP/HTTPS) を作成します。
  • リスナーは必要に応じてHTTPSを追加してください。
  • サブネットはふたつ必要です。

ロードバランサーの作成___EC2_Management_Console.png

次に証明書とセキュリティグループを選択します。

セキュリティグループは当然、ポート80と443でのインバウンド接続を許可していください。

新しいターゲットグループとして、種類=Lambda関数を選択します。

ロードバランサーの作成___EC2_Management_Console.png

最後に作成したLambda関数を選択します。

ロードバランサーの作成___EC2_Management_Console.png

ついにMovable Type Serverlessにアクセス

DNSの反映に少し時間がかかるようですが、ブラウザで次のURLを開くと…

http://(ロードバランサーのDNS名)/mt/mt.cgi

き…きたー! MTのログイン画面!

Sign_in___Movable_Type_Pro.png

セットアップ時に登録したユーザーでログインすると…

Dashboard___Movable_Type_Pro.png

ダッシュボードだー!

しかも意外と動作が軽快。

PSGIをインターフェースとして使っていますが、ライフサイクルとしてはリクエストごとにプロセスが発生するので、挙動としてはCGIに近いはずです。もしかしたらLambdaサービスはPSGI的にプロセスがリクエスト間で使い回しているかもですが。

でもコンテナを読み込むオーバーヘッドは感じられず、実用性も期待できます。

続きを期待します

他の動作確認はしていません。そもそもEFSなどでファイルシステムを永続化しないとサイトも公開できません。

でも使った分だけ課金、並列爆速再構築など、期待が膨らみますね。

あなたがここまで読んだということは、私はすでに力尽きているだろう。ここから先のチャレンジは、あなたに道を譲ろうと思う。検討を祈る!

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

RaspberryPI4をサーバーにして遊び倒す。その1

この記事は Voicy Advent Calendar 2020 の 11日目の記事です。
前日は @yamagenii さんの 一瞬で作るストリームデータ収集基盤でした。明日は, @y_katsumura さんの 採用から組織開発は始まっている 〜エントリーマネジメントの重要性〜 です。

はじめに

とりあえず買ったはいいがどうやって使うか迷いがちなRaspberry。

Raspberry Pi4は1GbEをフルスピードで扱えたり、USB3.0に対応するなどIO周りが強化されていて省電力なファイルサーバーとして使えそうな構成になっています。最近のアップデートは、64bitOSでUSBBootにも対応しているので、SSBから起動することによって、SDカードの書き込み最大回数をきにすることなく、またSSD化でディスクアクセス速度の向上もできるので、大分実用的に運用できるようになってきてるように思います。

ということで、なにを作ればよいかわからない人は、とりあえずサーバーにしてあそんでみるのはどうでしょうか。実用的だし、学習にもぴったりです。

この記事では、RaspberryPi4をNAS、クラウドストレージ、ストリーミングサーバーとして活用するところまで書いていきます。
書いているうちに長くなってきたので、2つに分けます。

必要なもの

  • Raspberry Pi4
  • Boot用のSSD (容量お好みで。うちは32GB使っています)
  • USB接続のSSDケースか、SATA-USB 変換ケーブル
  • データ用のHDD/SSD
  • USB3.0対応のHDD/SSDのケース

Raspberry Piのセットアップ

軽量化の為にデスクトップ機能は省きます。
Raspberry Pi OS Lite 64bit版をダウンロードして、EtcherでSSDにコピーしましょう。

WiFi設定とsshの有効化

コピーしたSSDのbootにWiFi設定とSSHの有効化をします。
有線でつないでいる場合には、WiFi設定は飛ばしてかまいません。

SSHの設定は、bootにsshのファイルを作成するだけで有効になります

$touch boot/ssh

WiFi設定は/bootにwpa_supplicant.confファイルを作成します。

$vi boot/wpa_supplicant.confg
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
    ssid="<アクセスポイントのSSID>"
    psk="<パスワード>"
}

起動して初期設定とアップデート

起動の際に、SDカードがない場合は、USBを読みにいくので、SSDをUSBにつなげて起動して、必要な初期設定を実行します。
ここでアップデートもして、一度再起動します。

sudo apt update
sudo apt dist-upgrade
sudo reboot

Openmediavalut5をインストール

piユーザーで行ってください。
インストールに時間がかかるのでインストールされるまで待ちます。
ドキュメントによると30分かかることもあるらしいですが、そんなにはかからなかった気がします。

wget -O - https://github.com/OpenMediaVault-Plugin-Developers/installScript/raw/master/install | sudo bash

インストールが終わったら、もう一度再起動します。

sudo reboot

再起動から復帰したあと、今までのインストール作業がうまくいっていれば、ブラウザからのアクセスでログインできます。
IPアドレスが判明してる場合にはそのIPアドレスから、そうでなければ「ホスト名.local」からアクセスできるはずです。
デフォルトのホスト名だと下記になります。

http://raspberrypi.local

ブラウザからアクセスしたらログイン画面がでるので、初期ユーザー名と初期パスワードでログインします。
- 初期ユーザー名:admin
- 初期パスワード:openmediavault

スクリーンショット 2020-12-12 105147.png

こんな画面がでたら成功です

スクリーンショット 2020-12-12 105553.png

NASの設定

左側のペインの項目から設定していきますが、ペインの項目すべてを設定しなくても、NASとして動作させられます。
最低限として以下の項目を設定します。
- 基本設定としてシステムのすべて
- ストレージのディスク、SMART、ファイルシステム
- アクセス権の管理のユーザー、共有フォルダ
- サービスのSMB/CIFS
順々に設定していきます。

システム

一般設定

システム→一般設定→Web管理者パスワード
からログインしたadminのパスワードを変更します。新しいパスワードを入れて保存、適用します。
スクリーンショット 2020-12-12 115449.png

日付と時刻

タイムゾーンのプルダウンから、自分の環境に合わせて選択します。
スクリーンショット 2020-12-12 110614.png

ネットワーク

ネットワークでは一般とインターフェースのタブから設定を変更します。

一般では、ホスト名とドメイン名を任意のものに変更します。
スクリーンショット 2020-12-12 110152.png

インターフェースでは、自分のネットワーク環境に合わせてネットワーク接続の設定をしていきます。
スクリーンショット 2020-12-12 110711.png

モニタリング

システム→モニタリングから設定します。
モニタリングは有効(緑色)にするだけです。
SMARTの設定を有効にするのに必要です。

スクリーンショット 2020-12-12 115913.png

ストレージ

ディスク

外付けのHDDなどを制御します。
「編集」では、電源管理などのオプションが選べます。
「ワイプ」では、ストレージの内容を消去できます。新しいHDDをフォーマットしたりするのに使用します。

スクリーンショット 2020-12-12 120019.png

S.M.A.R.T

設定タブで有効にするだけです。

スクリーンショット 2020-12-12 120214.png

その後、デバイスタブでHDDを選択して編集と押すと、有効化できます。
設定の際は、一台づつ、保存→適用してください。

スクリーンショット 2020-12-12 120324.png

ファイルシステム

Linux系では標準になっているとおり、外付けのストレージはまずマウントをしないと使えません。
「デバイス」の項目にある「/dev/sda1, /dev/sdb1」などがストレージです。
「利用可能」の項目に「n/a」「マウント済み」に「いいえ」と表示してあれば、つながっていない状態です。
デバイスを選択して、マウントボタンを押してマウントします。

マウントできないとエラーがでる場合には

  • フォーマット形式が異なる
  • 別のシステムから移行したストレージ
  • 未フォーマット

などの場合が挙げられます。
一度ストレージの中身を真っ新にする必要があるので、ディスク項目のワイプでストレージの中身を削除することで、ここで利用可能になります。
ワイプする際のポイントとしては、接続するデータ用のストレージのフォーマットはext4にしてください。
ntfs、exfat等、他のファイルシステムでも一応は機能しますが、いまのところ細かい不具合やバグなんかで動作が不安定です。
設定する際は、1台づつ保存→適用するようにしてください。

マウントできると以下のように、「利用可能」の項目に「ファイルサイズ」、「マウント済み」に「はい」と表示されるようになります。

ストレージをマウントしただけでは、NASとしては利用できないので、後ほど、共有フォルダの設定をする必要があります。

スクリーンショット 2020-12-12 120749.png

アクセス権の管理

samba上でネットワークドライブとして認識させるために、

  • ユーザーの作成
  • 共有フォルダの指定
  • sambaで共有する設定

上記の3つを設定していきます。

ユーザー

ユーザーにはRaspberry Pi OSのデフォルトユーザーのpiがすでにいますが、ネットワークから参照するのに使う専用のユーザーを作成します。
アクセス権の管理→ユーザーから追加していきます。
名前とパスワード、グループを設定してください。グループはとりあえずusersの指定があれば大丈夫です。
スクリーンショット 2020-12-12 121809.png

共有フォルダ

後ほど、sambaの設定で、sambaで共有するフォルダを選択しないといけません。
そのために、共有するフォルダを作成する必要があります。
アクセス権の管理→共有フォルダから設定していきます。

デバイスのプルダウンメニューに、さきほどマウントしたストレージがでてくるので、デバイスを選択して、名前とパスを設定します。
ストレージ全体を共有したい場合は、パスは「/」のみで大丈夫です。

スクリーンショット 2020-12-12 122130.png

サービス

サービスにはFTP,NFS,RSYNC,SMB/CIFS,SSHといろいろと設定したい項目がありますが、とりあえずNASとして活用するにはSambaの設定だけあれば大丈夫です。

SMB/CIFS

まずは、sambaサービスを有効にします。

スクリーンショット 2020-12-12 122545.png

次に、共有タブでSambaで共有するフォルダを選択します。
追加ボタンで出てきた項目のうち、さきほど設定した共有フォルダを指定します。
下にスクロールすると、その他、読み込み設定やパーミッションの設定もできるので任意に指定してください。単に共有するだけであれば、デフォルトのままで大丈夫です。
スクリーンショット 2020-12-12 122756.png

共有フォルダが追加されると、下記のようにリストにでてきます。
スクリーンショット 2020-12-12 122955.png

ここまでの設定が正しく反映されていれば、SAMBAで接続できるようになっているはずです。
NASとしての設定は以上です。

次に、クラウドストレージとストリーミングサーバーの構築に必要な環境をセットアップしていきます。

DockerとPortainerのインストール

サーバーを追加していくにあたって、OpenmediavaultではDockerとPortainer(DockerのGUI管理)をサポートしていて非常に便利なのでインストールしていきます。
OMV-ExtrasのペインのDockerのタブに移動して、プルダウンからDockerとPortainerをそれぞれインストールします。

スクリーンショット 2020-12-12 111929.png

インストールが終了したら、Open Portainerのプルダウンメニューを使って、Portainerにブラウザからアクセスします。
ログインユーザー名とパスワードを設定するよう誘導されるのでそれに従い、設定していきます。
こんな画面が出たら導入成功です。

スクリーンショット 2020-12-12 112436.png

あとは、基本的には、Dockerを使って、好きなサーバーをずいずい構築していけばOKです。
クラウドストレージとストリーミングサーバーの導入については、その2に続きます。

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

Portainerで複数ホストのコンテナを一元管理してPortainerはいいぞと言う

この記事はDocker Advent Calendar 2020の12日目の記事として書かれました。

結論

Portainerはいいぞ。

環境

  • Docker CE 19.03.13
  • Docker Compose 1.27.4
  • Portainer 2.0.0

Portainerとは

DockerコンテナやイメージなどをGUIで管理できるツールです。
小洒落たUIとシンプルな操作性で、入門者から上級者まで幅広い層にオススメできるツールです。
portainer00.png
公式のデモ環境があるので、一度触ってみてください。
http://demo.portainer.io/
ID:admin
PW:tryportainer
(参考:公式Githubリポジトリ)

基本的な使い方

まずはPotainerを使ってみましょう。
Portainer自体がDockerイメージとして提供されているため、以下のようにコンテナを起動するだけでOKです。

$ docker run \
    --detach \
    --publish 9000:9000 \
    --publish 8000:8000 \
    --name portainer \
    --restart always \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume portainer_data:/data \
    portainer/portainer-ce

portainer_data は登録した情報(ログインユーザの情報など)を永続化するためのボリュームです。

http://<ホスト名>:9000 にアクセスすると、以下のようにパスワードの設定を求められます。適当な値を設定してください。
startup01.png
続いて管理対象の環境を選択します。[Docker] を選び [Connect] を押します。
問題なければHome画面にlocalなるエンドポイントが表示されます。

local をクリックすると ContainersImages 等のリンクが表示されます。一旦 Containers を確認すると、以下のようにPortainerのコンテナが正常に動作している様子を確認できます。
startup04.png
コンテナを追加すると自動的にPortainerの管理対象に入ります。適当にnginxを立ち上げましょう。

$ docker run -dit -p 8080:80 --name nginx nginx

特にエラーが出なければ、コンテナ一覧にnginxがあることが確認できるでしょう。

各コンテナの画面ではlogsでログ確認、inspectでコンテナの情報確認、statsでリソース使用量等の確認ができます。また、コンテナ起動時に--interactive, --tty (-it)オプションを設定していればconsoleでコンテナ内部に対して操作を行うことができます。試しに先ほど起動したnginxに対してconsoleでアクセスします。

コンテナ一覧から [nginx] -> [Console] とアクセスし、 [Connect] を押すと以下のようにコンテナ内に入れます。とてもお手軽です。
nginx02.png
基本的な使い方を確認できたところで、一旦Portainerを削除します。

$ docker stop portainer && docker rm portainer

さて、ここまででPortainerで単一ホストのコンテナを管理する方法が分かりました。ただ、実際にはDockerホストは複数のノードにまたがっていることもあるでしょう。

この記事ではDocker Swarmで作成されたクラスタを単一のPortainerで管理する方法について説明します。

Docker Swarmで構築したクラスタのコンテナを一元管理する

1. Swarmの準備

今回は図のようなクラスタを構築しました。
cluster.png

名称 役割 説明
ip-172-31-36-96 MANAGER Swarmのマネージャノード
ip-172-31-32-220 WORKER Swarmのワーカーノード
ip-172-31-40-65 WORKER Swarmのワーカーノード

manager1台、worker2台という構成です。

Dockerホストはdocker-machineで用意するもよし、実際に何らかのインスタンスを立てるもよしです。
今回はmanagerもworkerもAWSで雑にインスタンスを立ててDockerとDocker Composeを入れました。1.27.4の部分は公式ガイドを参考に適宜最新版に置き換えてください。

$ sudo yum -y install docker
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

(注意) クラスタ内の各ノード間で以下のポートにアクセスできるよう設定する必要があります。クラウドサービスでホストを立てたりすると恐らくデフォルトでは閉じているはずなので、各ノードが相互に以下のポートを利用できるよう設定します。

ポート番号 プロトコル 用途
2377 TCP クラスタ管理通信
7946 TCP ノード間通信
7946 UDP ノード間通信
4789 UDP オーバーレイネットワーク通信

ネットワークの設定が完了したら、各ホストをSwarmのメンバーにします。

  • Swarmの作成@MANAGER
$ docker swarm init --advertise-addr <自身のIPアドレス>

<自身のIPアドレス>にはSwarmの他ノードから識別可能なIPアドレスを設定します。今回の例では172.31.36.96です。

  • Workerを参加させるためのトークンを発行@MANAGER
$ docker swarm join-token worker -q

SWMTKN から始まるトークンが表示されます。 -q はトークンのみ表示するというオプションで、あってもなくてもいいです。(コピペが楽なので付けています)

  • 各WorkerをSwarmに参加させる@WORKER
$ docker swarm join --token <トークン> <managerのIPアドレス>

<トークン>は先ほど作成したSWMTKN から始まるものです。はSwarm作成時のmanagerの<自身のIPアドレス>と同じものを指定してください。

  • 設定の確認@MANAGER Swarmを正常に設定できていれば、ノード一覧に3つのホストが表示されます。
$ docker node ls
ID                            HOSTNAME                                           STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
v6zghq4r796dbg2ixow620um9     ip-172-31-32-220.ap-northeast-1.compute.internal   Ready               Active                                  19.03.13-ce
7gbjabs2eg0rqjhvkkdqwr4dq *   ip-172-31-36-96.ap-northeast-1.compute.internal    Ready               Active              Leader              19.03.13-ce
xwl862sqjb5n2hs5z6nxmdz99     ip-172-31-40-65.ap-northeast-1.compute.internal    Ready               Active                                  19.03.13-ce

これでクラスタが完成しました。

2.Stackのデプロイ

StackはServiceの集合です。大雑把に捉えるとDocker Swarmはサーバを束ねる技術でDocker Stackはアプリケーションを束ねる技術です。

公式でPortainerのStackを構築するためのymlファイルが提供されているので、curlでダウンロードしてデプロイします。

$ curl -L https://downloads.portainer.io/portainer-agent-stack.yml -o portainer-agent-stack.yml
$ docker stack deploy --compose-file=portainer-agent-stack.yml portainer

デプロイが完了した後Portainerにアクセスすると、以下のように4つのコンテナが表示されます。Hostカラムから、3つの異なるホストにあるコンテナが1つのPortainerで管理できていることが分かります。

portainer01.png
Stackとは別に各ノードでnginxを立ちあげてみましょう。

$ docker run -dit -p 8080:80 --name nginx nginx

portainer02.png
すべてのnginxがきちんと表示されます。--interactive, --ttyオプションをつけて起動したので、Consoleを利用できます。当然、Portainerが所属しているノードとは別のノードのnginxのConsoleにもアクセスできます。
これで複数ノードのコンテナを一元管理できるようになったことを確認できました。

今回デプロイしたStackの内容を見てみましょう。

portainer-agent-stack.yml
version: '3.2'

services:
  agent:
    image: portainer/agent
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - agent_network
    deploy:
      mode: global
      placement:
        constraints: [node.platform.os == linux]

  portainer:
    image: portainer/portainer-ce
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    ports:
      - "9000:9000"
      - "8000:8000"
    volumes:
      - portainer_data:/data
    networks:
      - agent_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

networks:
  agent_network:
    driver: overlay
    attachable: true

volumes:
  portainer_data:

2つのサービスportaineragentを確認できます。portainerはGUIアプリケーションそのものです。agentは自ノードとPortainerの橋渡し役を担っています。
注目するべきはagentで定義されているmode: globalでしょう。これはクラスタの各ノードに該当サービスをデプロイするという指定です。各ノードにagentがいるおかげで、クラスタ内の全ノードのコンテナの情報を管理できるようになっています。

networksは両サービスが利用するネットワークを、volumesはPortainerのデータを永続化するボリュームを定義しています。どちらもデプロイ時になければ勝手に作成されます。

3. Stackの更新

ここまでで複数ホストのコンテナの一元管理を実現できました。
補足としてnginxをStackに組み込んでデプロイした時の挙動を紹介します。
一旦各ノードのnginxコンテナは削除し、サービスにnginxを突っ込んで再度デプロイします。

 portainer-agent-stack.yml
version: '3.2'

services:
  agent:
    image: portainer/agent
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - agent_network
    deploy:
      mode: global
      placement:
        constraints: [node.platform.os == linux]

  portainer:
    image: portainer/portainer-ce
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    ports:
      - "9000:9000"
      - "8000:8000"
    volumes:
      - portainer_data:/data
    networks:
      - agent_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

  web:
    image: nginx
    tty: true
    ports:
      - "80:80"
    networks:
      - agent_network
    deploy:
      mode: replicated
      replicas: 3

networks:
  agent_network:
    driver: overlay
    attachable: true

volumes:
  portainer_data:
$ docker stack deploy --compose-file=portainer-agent-stack.yml portainer

Portainerを確認すると、きちんとサービスwebがStackに組み込まれた状態でデプロイされていることが分かります。
portainer03.png

余談

Portainerはシンプルかつ高機能で、色々なことができます。いくつか「おー」と思ったものを紹介します。

  • テンプレートからアプリケーションの立上げ
    portainer04.png
    WordPressとかGitlabみたいなちょっと面倒なやつらもボタンポチポチで起動できます。

  • Stackのデプロイ
    portainer05.png
    ymlファイルをベタ書きするもよし、ymlファイルをアップロードするもよしです。

  • LDAP / OAuth認証
    portainer06.png
    既存の認証情報を利用することができます。

おわりに

色々と機能を紹介したものの、正直に言ってローカルホストのコンテナの管理機能だけでも相当にパワフルです。
特にConsole機能は頻繁に使います。CUIで操作していると「あれ、今ホストだっけゲストだっけ?」となることが度々あるので。。。

バージョン2.0からKubernetesに対応したらしいのでそちらも今後触ってみたいですね。
Kubernetes何もわからないので完全に理解したらまた関連記事を書こうと思います。

Portainerはいいぞ。

参考

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

Docker3.0.0をアップデートしたら、なんか起動しなくなった件

どうも、三町哲平です!

Docker Desktop Community 3.0.0という
Dockerの最新バージョンが2020-12-10に出ましたね!

Docker Desktopでは、最新バージョンが出るたびにアップデート通知がきますので、私も基本的に通知が来たらアップデートをしていました。

よくインターネットの書き込みなんかに○○のアプリをアップデートしたら不具合が起きたのでダウングレードしました。なんていうのを見る機会がありますが、私は今までその経験をしたことがなく、半ば都市伝説の様な半信半疑の感覚であり、他人ごとの様に感じていましたが、本日Docker Desktop Community 3.0.0で不具合が発生したので大慌てです。

エラー画面
Cannot start service ec-cube: Mounts denied
ERROR: Encountered errors while bringing up the project.

今まで通り、 $ docker-compose up でRailsの開発環境を立ち上げようとした所上記の様なエラーが発生して、使えたはずのdocker-composeの機能が使えなくなっていました...(汗)

ちなみに上記のエラー画面は、Docker For Macを3.0.0にアップデートしたらec-cube(コンテナ)起動しなくなった - Qiitaの記事の引用をさせていただいております。当時は、焦っていてエラー内容をスクショ等で控える余裕がなかったのですね。

まずは、正解から

どうも正解は、2つあり

1つ目が.

⇒その①3.0.0を使う場合
Docker Preference(設定) > Experimental Features > Use gRPC Fuse for file sharing をオフる(デフォルトではONになっています)

だそうです。
※ちなみにこの引用も下記の記事の引用です。そしてこの記事のほとんどが下記記事の引用になります。
Docker For Macを3.0.0にアップデートしたらec-cube(コンテナ)起動しなくなった - Qiita

スクリーンショット 2020-12-12 4.29.15.png

2つ目が.

=> その②2.5.系にダウングレードする

です。

ちなみに私は、解決策がわからなかったので、②2.5.系にダウングレードするを実行しました。

ダウングレードには、一度Docker Desktopをアンインストールした後に、
macユーザーだと、
Docker for Mac release notes | Docker Documentation

Windowsユーザーだと、
Docker for Windows release notes | Docker Documentation

から過去のバージョンを遡ってダウンロード→インストールまでできます。

まとめ

記事の構成まで、引用元のDocker For Macを3.0.0にアップデートしたらec-cube(コンテナ)起動しなくなった - Qiitaに似てしまいましたが、

  • 作業優先でろくにメジャーバージョンアップデートであることを確認しなかったのは私のミスです..
  • インストールするタイミングでコンテナSTOPしていなかったのが原因かもしれないです..

@mksm_wrk さんは書いていらっしゃいますが、この状況が私と全く同じです。

私もアップデートの通知が来た時にバージョンを一切気にせずアップデートしましたし、インストールする際にコンテナは、起動中で作業をバリバリしておりました。

しかし、ここで気にするべき点は、私は②2.5.系にダウングレードするを選択して、@mksm_wrk さんが①3.0.0を使う場合を選択できた理由が何なのかということです。

それは、

今回も世界の知見にすくわれました、ありがとう?

@mksm_wrk さんが書いている様に英語のサイトを参考にされているということです。

いわば、日本語ではない一次情報を取りに行っているといるいうのが私との明確な差として現れてしましました。

引用のさらに大元の記事:
Unable to mount protected Mac paths after upgrade to Docker 3.0.0 · Issue #5115 · docker/for-mac

これからは、翻訳機能を使いながらでも英語の記事を毛嫌いせずに読んでいかないといけなと素直に思いました。

3.0.1が出た!

なんと翌日の2020-12-11には、Docker Desktop Community 3.0.1が出ました。
スクリーンショット 2020-12-12 4.57.23.png

やっぱり英語はよくわからないので翻訳↓
スクリーンショット 2020-12-12 4.57.51.png

特定のディレクトリがコンテナにマウントできない問題を修正しました。docker / for-mac#5115を修正と書いているのでこれが今回の件の事なのでしょう。

因みに私のDockerのバージョンは、
2.5.1→3.0.0(今回のエラー発生)→2.5.0(別のエラー発生)→3.0.1(2.5.0の時と同じエラーの為、一から環境構築中)
となっております。

話は、脱線しましたが、2.5.系にダウングレードする事で今回の3.0.0でのエラーに関しては間違いなく解決されています。2.5.0(別のエラー発生)に関しては、機会があれば記事にしますが、たぶん正攻法の様なやり方をしていないので、日記みたいにな記事になりそうで、書くかどうか迷っています。

あと、更にもう一つ脱線として、
3.0.1が更新された時日付は、12-12なのに、Dockerの公式サイトでは。2020-12-11となっていてなんでだろうと思っていましたが、多分下記の様な理由かなと思います。
スクリーンショット 2020-12-12 2.48.57.png

日本はアメリカより半日以上早いと覚えておきましょう。

では、また。

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

Docker Desktop Mac 3.0.0 にアップデートで起動しなくなって、mysqlが動かない時の対応

前回の記事「Docker Desktop Mac 3.0.0 にアップデートしたら、起動しなくなった時の対応」の続きです。
mysqlが永久起動失敗ループに・・・

現象

下記のようなログを出力しながら、mysqlが起動→失敗→再起動を繰り返しています。

mysql             | 2020-12-11 14:28:26+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
mysql             | 2020-12-11 14:28:26+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.21-1debian10 started.
mysql             | 2020-12-11T14:28:26.491369Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.21) starting as process 1
mysql             | 2020-12-11T14:28:26.508161Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
mysql             | 2020-12-11T14:28:27.034000Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysql             | 2020-12-11T14:28:27.040556Z 1 [ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('0') and data dictionary ('2').
mysql             | 2020-12-11T14:28:27.040839Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
mysql             | 2020-12-11T14:28:27.041084Z 0 [ERROR] [MY-010119] [Server] Aborting
mysql             | 2020-12-11T14:28:27.601939Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.21)  MySQL Community Server - GPL.

stack overflow に同様の現象を発見

Mysql not starting in a docker container on MacOS after docker update
mysqlのデータディレクトリをホストのvolumesに指定していると、この状況になるのでしょうか。
2.3 から 2.4 にUpdateしたときに発生していた模様です。
今回現象が出ている環境は2.5で新規作成した環境ですから、初めて出くわしたのですね・・・
(埋蔵している過去のプロジェクトを復活することになった時、これが発生するのか・・・)

対応方法

stack overflowの記事の1つ目の回答の方法はダメでした。
2つ目の方法でやってみます。

1.Docker Desktop をダウングレード

1) ver. 3.0.0 をUninstall

Docker Desktop > 設定 > 右上の虫っぽいマーク > Uninstall

2) ver. 2.5.0.0 をインストール

Docker for Mac release notes2.5.0.0をダウンロードして、インストール。
※2.5.0.1のDownloadリンクのものは「最新版dmg」へのリンクなので、3.0.0でした・・・直しておいてほしいですね;

2.User gRPC FUSE for file sharing を オン

Dockerを起動して、バージョンが3.0.0でないことを確認し、
メニュー Docker Desktop > 設定 > Experimental Features の
User gRPC FUSE for file sharing を オン になっていることを確認

3.docker-compose up

無事、起動しました!
(って、このまま3.0.0にアップデートせずに使えば良いのでは・・・と悪魔が囁いたのですが、本記事と前回記事の意味がなくなりますので、頑張ってみようと思います。)

4.DBダンプ

どんな手段でも良いので、mysqlのDBダンプをとりましょう

5.docker-compose stop

止めます

6.mysqlのデータディレクトリを削除

mysqlコンテナ起動時に再作成してくれるそうです。
※僕は、念の為、renameにしましたが^^

7.Docker Desktop 3.0.0 にアップグレード

Docker Desktop > Check for Updates... が手っ取り早いですね。

8.Docker Desktop 起動

アップデート後にStartingのまま起動しない場合は、Docker Desktop > 設定 > 右上の虫っぽいマーク > Uninstall をした後、 ver. 3.0.0をダウンロードして再インストールしてみてください。

9.User gRPC FUSE for file sharing を オフ に設定

メニュー Docker Desktop > 設定 > Experimental Features の
User gRPC FUSE for file sharing を オフ に再度設定します

10.docker-compose up

無事、起動しましたか?

11.DBダンプからデータ復旧

どんな手段でも良いので、4のダンプでデータを復旧しましょう

今後は・・・

Volumeはコンテナのを使うことにします。
いま思えば、なぜホストのVolumeを割り当てていたのだろうか・・・

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

Docker Desktop Mac 3.0.0 にアップデートで起動しなくなって対応した後、mysqlが動かない時の対応

前回の記事「Docker Desktop Mac 3.0.0 にアップデートしたら、起動しなくなった時の対応」の続きです。
mysqlが永久起動失敗ループに・・・

現象

下記のようなログを出力しながら、mysqlが起動→失敗→再起動を繰り返しています。

mysql             | 2020-12-11 14:28:26+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
mysql             | 2020-12-11 14:28:26+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.21-1debian10 started.
mysql             | 2020-12-11T14:28:26.491369Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.21) starting as process 1
mysql             | 2020-12-11T14:28:26.508161Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
mysql             | 2020-12-11T14:28:27.034000Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysql             | 2020-12-11T14:28:27.040556Z 1 [ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('0') and data dictionary ('2').
mysql             | 2020-12-11T14:28:27.040839Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
mysql             | 2020-12-11T14:28:27.041084Z 0 [ERROR] [MY-010119] [Server] Aborting
mysql             | 2020-12-11T14:28:27.601939Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.21)  MySQL Community Server - GPL.

stack overflow に同様の現象を発見

Mysql not starting in a docker container on MacOS after docker update
mysqlのデータディレクトリをホストのvolumesに指定していると、この状況になるのでしょうか。
2.3 から 2.4 にUpdateしたときに発生していた模様です。
今回現象が出ている環境は2.5で新規作成した環境ですから、初めて出くわしたのですね・・・
(埋蔵している過去のプロジェクトを復活することになった時、これが発生するのか・・・)

対応方法

stack overflowの記事の1つ目の回答の方法はダメでした。
2つ目の方法でやってみます。
要するに、Dockerのバージョン戻して、データ救出セヨ!です^^

1.Docker Desktop をダウングレード

1) ver. 3.0.0 をUninstall

Docker Desktop > 設定 > 右上の虫っぽいマーク > Uninstall

2) ver. 2.5.0.0 をインストール

Docker for Mac release notes2.5.0.0をダウンロードして、インストール。
※2.5.0.1のDownloadリンクのものは「最新版dmg」へのリンクなので、3.0.0でした・・・直しておいてほしいですね;

2.User gRPC FUSE for file sharing を オン

Dockerを起動して、バージョンが3.0.0でないことを確認し、
メニュー Docker Desktop > 設定 > Experimental Features の
User gRPC FUSE for file sharing を オン になっていることを確認

3.docker-compose up

無事、起動しました!
(って、このまま3.0.0にアップデートせずに使えば良いのでは・・・と悪魔が囁いたのですが、本記事と前回記事の意味がなくなりますので、頑張ってみようと思います。)

4.DBダンプ

どんな手段でも良いので、mysqlのDBダンプをとりましょう

5.docker-compose stop

止めます

6.mysqlのデータディレクトリを削除

mysqlコンテナ起動時に再作成してくれるそうです。
※僕は、念の為、renameにしましたが^^

7.Docker Desktop 3.0.0 にアップグレード

Docker Desktop > Check for Updates... が手っ取り早いですね。

8.Docker Desktop 起動

アップデート後にStartingのまま起動しない場合は、Docker Desktop > 設定 > 右上の虫っぽいマーク > Uninstall をした後、 ver. 3.0.0をダウンロードして再インストールしてみてください。

9.User gRPC FUSE for file sharing を オフ に設定

メニュー Docker Desktop > 設定 > Experimental Features の
User gRPC FUSE for file sharing を オフ に再度設定します

10.docker-compose up

無事、起動しましたか?

11.DBダンプからデータ復旧

どんな手段でも良いので、4のダンプでデータを復旧しましょう

今後は・・・

Volumeはコンテナのを使うことにします。
いま思えば、なぜホストのVolumeを割り当てていたのだろうか・・・


追記:

いま思えば、なぜホストのVolumeを割り当てていたのだろうか・・・

これ、Laradockの標準がこうなっていたから、オリジナルでdocker-compose.ymlを書くときに参考にしたのでした。
全世界のMacでLaradock使いの方々が阿鼻叫喚ってことですか!?

個人的に他にも該当プロジェクトが多数あります・・・
これはやはり2.5に戻s...うぅん、なんでもないです。

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

Dockerって何よ

初めに

実験的にrailsアプリにDockerを導入してみましたが、根本的な理解がまだまだなので、そもそもDockerが何者なのか自分用メモとして残しておきます。

Dockerって?

Dockerは、コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアあるいはオープンプラットフォームである。 Dockerはコンテナ仮想化を用いたOSレベルの仮想化によりアプリケーションを開発・実行環境から隔離し、アプリケーションの素早い提供を可能にする。 (wikipedia引用)

つまり仮想環境をPC上に作って、そこでアプリを動かせるよってことですね。

何が良いのか?

Dockerを使うメリットは以下の通りです。

1.実行環境の立ち上がりが早い

従来のホスト型仮想化では仮想ハードウェア上でOSを動かすために、ネイティブ環境と比べて動作が遅くなりがちでした。一方でコンテナ型仮想化(Docker)では起動しているコンテナで動かすので軽量です。(ゲストOSが不要)

2.Dockerfileの共有で違うPCに同じ環境を作成できる

一度Dockerfileを書いてしまえば、他人と共有することで簡単に同じ環境を整えることが出来ます。新規のメンバー参入時に即座に同じ環境が構築できるのは便利ですね。

3.環境の移動が手軽

開発環境では問題なく動作していたのに、本番環境ではちょっとした環境差分で動作しないことがあります。しかし、開発からDockerを使用することで、開発環境と本番環境の差異を吸収してくれます。つまり、アプリの再現性を高めることが可能になるということです。

試しに使ってみる

公式サイトからお使いのOSに合ったDocker Desktopをインストールします。
インストール完了後に一応バージョン確認をします。

$ docker -v
Docker version 19.03.13, build 4484c46d9d

無事にインストール出来ていますね。

Dockerの動作確認用のコンテナとして、hello-worldコンテナがあります。docker runコマンドでイメージが取得できますので、試しにやってみます。

$ docker run hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
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.
    (amd64)
 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/

hello-worldコンテナはあくまで動作確認なので、ターミナル上に文字が表示されるだけです。
コマンドを実行した時、裏では以下のようなことが起きています。

1.Dockerクライアント(コマンド)でコンテナを要求する
                    ↓
2.Dockerデーモンがネットを経由してコマンドで指定されたイメージを探す 
                    ↓
3.Dockerhubに該当するイメージが有ったらデーモンに返す
                    ↓
4.Dockerデーモンはクライアントに実行結果を送る

尚、PC上に既にイメージがある場合は2~4は省略されます。

また、docker runは一連のコマンドを一まとめにした便利なコマンドです。

docker run = docker pull(イメージ取得) + create(コンテナ作成) + start(コンテナ起動)

次に先程取得したhello-worldイメージを確認してみます。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              bf756fb1ae65        11 months ago       13.3kB

hello-worldイメージが入っています。次にコンテナの方も見ていきますが、hello-worldコンテナは文字を出力したら勝手に停止するので、オプションで停止中のコンテナも見れるようにします。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
31d04b2e2868        hello-world         "/hello"            14 minutes ago      Exited (0) 14 minutes ago                       determined_mahavira

最後にイメージとコンテナを削除します。

$ docker rm 31d04b2e2868(コンテナID)
31d04b2e2868
$ docker rmi hello-world(イメージ名)
Untagged: hello-world:latest
Untagged: hello-world@sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
Deleted: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b
Deleted: sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63

無事に削除が完了しました。次回はより実践的な使用例に入っていきたいと思います。

参考にさせていただいた記事

入門Docker

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

VisualStudio CodeからMongoDBにつなげてみよう!

はじめに

この記事は、Visual Studio Code Advent Calendar 2020 の11日目の記事になります。
今回は、表題の通りMongoDB for VS Codeを使ってMongoDBに接続してみるというお話です。(※VisualStudio Code は、以下VSCodeと記載します)

ところでなぜMongoDB?

ただいま個人学習として、MondoDB公式が提供している、MongoDB Universityというオンライン学習コースをコツコツ進めています。12

MongoDBのクライアントとしては、MongoDB Compassという非常に優れたツールがあります。ただ、コーディングしながらであれば、データの簡単な確認はエディタ内で済ませたい...。
また、コースが進むにつれ、APIのみでの操作でなく、MongoDBのプロセスや運用面のトピックも出てきたので、もう少しデータファイルなども見える方が良い。

幸い、Dockerの公式イメージもあるので、ローカル開発環境での起動にはDockerが使えます。このあたりを踏まえて、「全部VSCode内で完結するかしら?」と思ってやってみた顛末となります。

まずはMongoDBのDockerイメージを使おう!

実のところ、MongoDB Atlasというクラウドのサービスを使えば、無償枠でもサービスとしてはMondoDBの操作が可能なので、MongoDB Extensionを試すのにはローカルでDockerのMongoDBを起動する必要はありません。

ただし、上記の通り、プロセスやデータファイルの管理の仕方も合わせて確認したいので、Dockerで起動してみることにします。

公式のイメージから起動するよ!

イメージは、こちらを使います。

VSCodeのDocker Extensionも非常に便利で日々進化していて、これがないとわたしはお仕事ができません...。3
わたしの環境はMacですので、DockerのホストとしてはDocker for Macを利用します。

# まずはイメージを引っ張ってきます
docker pull mongo

イメージを取得後、VSCodeのDocker Extensionを使うと、Dockerイメージの一覧や起動中のコンテナが表示できます。
MondoDBのイメージは、そのままVSCodeから右クリックで起動ができます。

コンテナが立ち上がると、上部にMongoDBのコンテナが表示されます。
ここからさらに右クリックで起動中のコンテナ内にアクセスしたり、ログを確認することができます。
コンテナ内のファイルシステムも、VSCodeから表示できるようになっています。

コンテナ内に入ってMongo Shellを利用してみよう!

VSCodeのコンテナの表示から、右クリックでコンテナにアタッチします。(Attach Shellで、実際は docker execコマンドが走ります)

MondoDBの公式イメージは、シェルにアクセスできるので、コンテナ内でpsコマンドでMondoDBのプロセスが上がっていることを確認してみました。

内部には、もちろんMongoDB Shell (データベースにアクセスするためのCUIのクライアント)が入っているので、単純に mongo コマンドを打つだけで、操作が可能になります。

ちょっと長いですが、コンテナ内での結果を貼ってみます。

# mongoコマンドで起動(内部からでパスワード無し)
root@b0c9ee280d35:/# mongo
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("fec4ef7e-b4b4-4b55-937c-1ccf82510de9") }
MongoDB server version: 4.4.2
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        https://docs.mongodb.com/

...[一部略]...

---
> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
> use test
switched to db test
> show collections
sales

> db.sales.findOne()
{
        "_id" : 1,
        "item" : "abc",
        "price" : 10,
        "quantity" : 2,
        "date" : ISODate("2014-03-01T08:00:00Z")
}

データベースの一覧表示と、test DBへの切り替えができました。

また、デフォルトで /data/db 以下にあるデータファイルも見てみます。

root@b0c9ee280d35:/data/db# pwd
/data/db
root@b0c9ee280d35:/data/db# ls -w 1
WiredTiger
WiredTiger.lock
WiredTiger.turtle
WiredTiger.wt
WiredTigerHS.wt
_mdb_catalog.wt
collection-0--359143437334718796.wt
collection-13--359143437334718796.wt
collection-2--359143437334718796.wt
collection-4--359143437334718796.wt
diagnostic.data
index-1--359143437334718796.wt
index-14--359143437334718796.wt
index-3--359143437334718796.wt
index-5--359143437334718796.wt
index-6--359143437334718796.wt
journal
mongod.lock
sizeStorer.wt
storage.bson

MongoDBのストレージエンジンはWiredTigerを使っているので、WiredTiger.*なファイルがあるのが確認できました。
コレクション(データベースに相当)するファイル、インデックスファイルは、拡張子が .wt になっています。

Docker Extensionは便利ですね!

MongoDB for VS Codeを使ってみよう!

長くなりましたが、ここからが本題です。
まずは拡張機能のメニューから、MongoDBと検索し、追加してみましょう。
20201211時点では、プレビュー版となっていますが、それでもだいぶいろんなことができます!

20201211時点での機能は、以下の通り。

  • MongoDB Atlas、もしくは任意のMondoDBに接続ができる
    • コレクション(テーブルに相当)の表示
    • スキーマの表示
    • ドキュメント(レコードに相当)の表示
  • MongoDB Playgrounds という機能で、エディタ上に記載したMongoDB用のクエリを実行し、データの追加や検索、アグリゲーションの操作ができる
  • MongoDB Shellの起動ができる4
  • Terraformを使って、MondoDB Atlas上のデータベースの構成を管理できる

GitHub上のIssue (Feature)もたくさんあって、今後ますます楽しみです!

まずは接続をしてみよう!

Dockerで起動したMongoDBは、特に指定がなくても以下のコマンドで起動してくれるので、Mac側にポート27017がフォワードされるため、localhost:27017を介して接続できるようになります。

docker run --rm -d  -p 27017:27017/tcp mongo:latest

拡張機能を追加すると、葉っぱのマークが左のバーに表示されるので、そこからMongoDB操作用のパネルに切り替えができます。

接続設定を行うため、Connectionsのメニューから右クリックで Add MongoDB Connection を選ぶと、接続設定用の画面が表示されます。

単純に起動した場合は、パスワードは設定無しの状態で起動しますので、接続設定には、以下を記載してボタンを押せばOKです。5

  • Hostname: localhost
  • Port: 27017

※PCやご利用の環境でファイアウォールやセキュリティソフトがPort 27017の通信をブロックしていると、正しく接続できないのでご注意を!

うまくいくと、左側にデータベースの一覧が表示されます。
まだ管理用のものしかありません。

各データベースをクリックすると、テーブルに相当するコレクションが表示されます。
次々とクリックしていくと、さらにインデックスやスキーマ、データも表示できます。

MongoDB Playgroundsを使ってデータベースを作成してみよう!

おそらく何らかのデータベース用のGUIクライアントを使ったことがある方は、あとは直感的に操作がわかると思います。

さて、管理用のデータベースだけでは困るので、データベースの作成をしてみたい。
MongoDB Shellを使ってデータベース作成もできますが、この拡張機能には、MongoDB Playgrounds という機能があります。

操作パネルの下の方に、Create New Playgroundsというボタンがあるのでクリックすると、MongoDB操作用のサンプルスクリプトが書かれた画面が表示されます。
この言語はMQL(MongoDB Query Language)と言われるものです。6

この画面には、こんな内容が書かれています。
内容は、こんな感じ。

  • 任意のデータベース(ここではmongodbVSCodePlaygroundDB)に接続
  • データベースのsalesというコレクションにデータを追加
    • ちなみに、デフォルトではコレクションが存在しない場合は、勝手に作成されます
  • データ登録後、find() クエリで検索を実行します
  • aggregationを使って、検索&集計をします
// Select the database to use.
use('mongodbVSCodePlaygroundDB');

// Insert a few documents into the sales collection.
db.sales.insertMany([
  { '_id' : 1, 'item' : 'abc', 'price' : 10, 'quantity' : 2, 'date' : new Date('2014-03-01T08:00:00Z') },
  { '_id' : 2, 'item' : 'jkl', 'price' : 20, 'quantity' : 1, 'date' : new Date('2014-03-01T09:00:00Z') },
  { '_id' : 3, 'item' : 'xyz', 'price' : 5, 'quantity' : 10, 'date' : new Date('2014-03-15T09:00:00Z') },
  { '_id' : 4, 'item' : 'xyz', 'price' : 5, 'quantity' :  20, 'date' : new Date('2014-04-04T11:21:39.736Z') },
  { '_id' : 5, 'item' : 'abc', 'price' : 10, 'quantity' : 10, 'date' : new Date('2014-04-04T21:23:13.331Z') },
  { '_id' : 6, 'item' : 'def', 'price' : 7.5, 'quantity': 5, 'date' : new Date('2015-06-04T05:08:13Z') },
  { '_id' : 7, 'item' : 'def', 'price' : 7.5, 'quantity': 10, 'date' : new Date('2015-09-10T08:43:00Z') },
  { '_id' : 8, 'item' : 'abc', 'price' : 10, 'quantity' : 5, 'date' : new Date('2016-02-06T20:20:13Z') },
]);

// Run a find command to view items sold on April 4th, 2014.
db.sales.find({ date: { $gte: new Date('2014-04-04'), $lt: new Date('2014-04-05') } });

// Run an aggregation to view total sales for each product in 2014.
const aggregation = [
  { $match: { date: { $gte: new Date('2014-01-01'), $lt: new Date('2015-01-01') } } },
  { $group: { _id : '$item', totalSaleAmount: { $sum: { $multiply: [ '$price', '$quantity' ] } } } }
];
db.sales.aggregate(aggregation);

この内容を、直にMongoDB Shellに貼り付けても同じことはできますが、MongoDB Playgroundsを使うと、画面上部に▶︎の実行マークが表示され、そのマークを押すと、MongoDBに対し上記の処理が実行されます。

または、実行したい箇所(行)単位を選択すると、うっすらと▶︎の実行マークが表示され、その箇所だけが実行されます。

Playgroundのパネル上の処理を実行後、MongoDBの操作パネル側からデータを確認すると、salesというデータベースが作成され、8件追加されたのがわかります。

また、選択箇所単位での実行が可能で、実行結果は別パネルに表示されます。

このPlaygroundのパネル上の内容は書き換えた上での実行が可能なので、MongoDB Shellを使わずに、複雑なクエリを投げたい場合には特に重宝すると思います。

まとめ

以上、ざっくりと機能紹介をさせていただきました。

MongoDB for VS Codeの操作パネルの下側には、ドキュメントへのリンクや、GitHub Issueへのリンク、それからMongoDB Atlas上にクラスタを作るショートカットも用意されています。

まだまだ開発途上なので、もう少し使い倒して、なにかフィードバックできたらなと思っています。

今回は合わせてDocker Extensionについても触れています。
Dockerの技術は日々進歩していて、大規模な管理の仕組みはわたしは全く追いつけていないのですが、イメージやコンテナの基本操作は、GUIの力を借りて確認しながら覚えていけます。
アプリケーション開発に取り組む方は、ぜひVSCodeと合わせて使ってみてくださいね!


  1. MongoDBの基本を学習できるオンラインでの学習コースです。基本コースから開発者、管理者向けにコースがわかれていき、データモデリングやセキュリティ、デバッグといった範囲も扱います。 

  2. ひとりMongoDB University」と題して学習記録を付けています。 

  3. いまや、Dockerコンテナの中に入って直にVSCodeでコンテナ内のファイルが編集できるようになっています。(https://code.visualstudio.com/docs/remote/containers

  4. ローカルのMondoDB Shell (mongosh) を呼び出してくれます。このため、mongoshのインストールとVSCodeへのパスの追加が必要です。 

  5. 実運用ではこの状態はよろしくないので、authentication is enabled なモードで起動をしましょう...。 

  6. https://developer.mongodb.com/how-to/getting-started-atlas-mongodb-query-language-mql に、このPlaygroundでMQLを利用する方法が詳細に紹介されています。(英語) 

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