20210111のdockerに関する記事は23件です。

VSCode + Docker + PHP の開発環境を、XDebugでデバッグする。

はじめに

扱っていたプロジェクトは Laravel を使ったPHPアプリケーションで、VSCodeによって製作されていました。
Java や Swift を使った作業には慣れていたのですが、PHPのデバッグが久しぶりで大いにはまったので備忘録です。

やったこと

PHPのデバッグには XDebug を使うということなので、XDebug解説記事 をふむふむと読み(大変わかりやすい記事です)作業に当たりました。

たくさんの記事が丁寧に説明しているので詳細は端折りますが

php.ini
;...省略

[XDebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so
xdebug.default_enable=1
xdebug.remote_port=9000
xdebug.remote_autostart=1
xdebug.remote_handler=dbgp
xdebug.remote_connect_back=0
xdebug.remote_host=host.docker.internal
xdebug.remote_log=/tmp/xdebug.log
xdebug.idekey=VSCODE
Dockerfile
FROM php:7.4-apache

# XDebug
RUN pecl install xdebug \
  && docker-php-ext-enable xdebug

#...省略
launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "port": 9000,
      "log": true,
      "pathMappings": {
        "/var/www/html": "${workspaceFolder}/src"
      },
      "ignore": [
          "**/vendor/**/*.php"
      ]
    }
  ]
}

こんな感じで、PHP初期化、Docker初期化、XCodeのデバッグ設定を終えました。

これが全然びくとも動かない。

解決策

いろんなパラメータをいじり倒しましたが全然動かず諦めかけましたが、最終的には xdebug のバージョンの問題ということに気づきました。

$ php -v | grep -i "xdebug"
    with Xdebug v3.0.2, Copyright (c) 2002-2021, by Derick Rethans

Xdebug 2.x から 3.x で、設定値が様変わりしています。
https://xdebug.org/docs/upgrade_guide
このドキュメントに詳しくわかりやすく書いてあります。

たとえば、xdebug.remote_logxdebug.log に Rename されています。どうりで、ログもさっぱり出ないわけです。

細かい意味はそちらを見ていただくとして、上記の php.ini は正しくはこうなりました。

php.ini
;...省略

[XDebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so
xdebug.mode=debug
xdebug.client_port=9000
xdebug.start_with_request=yes
xdebug.discover_client_host=0
xdebug.client_host=host.docker.internal
xdebug.log=/tmp/xdebug.log

現場からは以上です。

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

Docker備忘録

Docker, docker-composeを使うときの備忘録。

最初にとても勉強になったサイト: docker と docker-compose の初歩 - Qiita

dockerコマンド

docker run [イメージ名]
docker ps # 実行中コンテナ一覧
docker ps -a # 実行中+終了したコンテナ一覧
docker rm [ID] # 終了したコンテナを削除
sudo docker ps -a
docker run --name [ラベル名] [コンテナ名] #ラベル名でコンテナを指定する
docker image ls #手元にある(ダウンロードの必要がない)dockerイメージを表示
docker rmi [イメージ名] #dockerイメージを削除
docker restart [ID] # 終了したコンテナを実行中に
docker attach [ID] # 実行中のコンテナに入る
docker exec [ID] [コマンド] #実行中のコンテナにコマンドを与える
docker exec -it [ID] /bin/bash # 実行中のコンテナに入る
docker commit [停止中の元イメージ名] [作るイメージ名] # 元イメージから作るイメージを作る

(以下は自分用に書き換える)

Dockerfile
FROM centos:7

RUN yum update -y \
    && yum install httpd -y \
    && echo "hello world from dockerfile" > /var/www/html/index2.html \
    && yum clean all

ADD ./httpd/www/index.html /var/www/html/

EXPOSE 80

ENTRYPOINT ["/usr/sbin/httpd", "-DFOREGROUND"]

docker-composeコマンド

docker-compose.ymlがあるディレクトリで

docker-compose up -d # Upにする
docker-compose down  # 停止中にする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker上のFRRoutingでMPLS-VPNを動かしてみる

はじめに

Docker上のFRRoutingでOSPFを動かしてみるに引き続き、個人のノートPCでお手軽に、なるべく省リソースでネットワークの勉強/検証をしたい、という動機から、Docker+FRRoutingを試してみています。今回はMPLS-VPNをやっていきます。
※誤り等ありましたらお気軽にコメントいただければと思います。

参考にしたページ

MPLSについてはこちらのページにお世話になっているので、検証構成とコンフィグ設計はこちらをベースにしました。

また、FRRoutingを使ったMPLSの環境構築については、こちらを参考にさせていただきました。

検証環境

  • Windows 10 (Core i7, 12GB RAM)
  • VirtualBox 6.1
  • Ubuntu 18.04 (2 Core, 4GB RAM, 10GB Disk)
  • Docker 20.10.2
  • FRRouting 7.5

Dockerまではインストールされた状態から始めます。

検証構成

image.png

MPLS-VPN 検証構成とコンフィグ設定(PE-CE間でスタティックルートを使用)に則った構成とアドレス設計です。違うのは、各ネットワークで.1ではなく.2を使っていること(Docker networkのbridge用アドレスを.1とするため)、PE1/PE2でコア側をeth0、ユーザ側を順にeth1, 2としている所です。

青字はdocker network(bridge)での実装におけるネットワーク名です。Dockerでは、複数のネットワークをコンテナにアタッチする際、GWやInternal等の属性が同じ場合は、ネットワーク名の辞書順にコンテナ内のeth0, 1, 2, ...に割り当てられるようです1 2。今回、PEルータではコア側をeth0、ユーザ側を順にeth1, 2, ...と統一したかったので、上記のような命名規則で対応することにしました。

全体の流れ

手順全体の流れと、設定対象機器は以下の通りです。

順番 内容 Pルータ PEルータ CEルータ
1 ホストOSの設定 - - -
2 Dockerネットワークとコンテナ作成
3 FRR daemonの設定 -
4 OSPFの設定 -
5 LDPの設定 -
6 MP-BGPの設定 - -
7 VRFの設定 - -
8 CEルータの設定 - -

1. ホストOSの設定

はじめに、ホストOS(Ubuntu VM)の設定をします。

公式docsに従って、MPLSのカーネルモジュールをロードします。
/etc/modules-load.d/modules.confを編集し、mpls_router、mpls_iptunnelを追加。

/etc/modules-load.d/modules.conf
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
mpls_router
mpls_iptunnel
# modprobe mpls-router mopls-iptunnel
modprobe: FATAL: Module mpls-router not found in directory /lib/modules/4.15.0-124-generic

エラーになったので、公式の記載に従って拡張パッケージをインストールします。

# apt install linux-modules-extra-`uname -r`

公式ではapt-get install linux-modules-extra-`uname -r`-genericとなっていましたが最後の-genericはいらないですね。

これで改めてカーネルモジュールをロードします。

# modprobe mpls-router mpls-iptunnel
# lsmod | grep mpls
mpls_router            28672  0
ip_tunnel              24576  1 mpls_router

無事ロードされました。
試しにコンテナを作って、コンテナ内でlsmodをすると、上記2つが反映されています。

# docker run -dit --name test alpine
# docker exec -it test lsmod | grep mpls
mpls_router            28672  0
ip_tunnel              24576  1 mpls_router

MPLS転送を有効にするためのカーネルパラメータの変更は、コンテナ内でやればいいようなので、後でやることにします。

2. Dockerネットワークとコンテナ作成

Docker Network(bridge)を使って独自ネットワークを作り、そこにコンテナをつないでいきます。

ただし、ここで1つ問題があります。Docker Networkを使ったネットワーク作成はお手軽なのですが、コンテナをつないだ際、IPアドレスが自動で設定されます。(もしくは--ipオプションで手動指定)。こうして自動もしくは手動でコンテナkernelによって設定されたIPアドレスは、FRRoutingを使って変更・削除することはできません。また、デフォルトルートを含む複数のルートも自動で設定されてしまい、FRRoutingからは変更・削除できません(かつ、Kernel Routeは最優先)。IPアドレスであればコンテナ作成の際に手動設定してしまい、FRRoutingからは触らないようにする、というのも可能ですが、最大限vtyshを使って設定したいですし、ルートの方はネットワーク設定に支障がある場合もありますので、ip addr delip route delコマンドで削除する必要があります。
※こうした自動設定を止める設定があるかもしれませんが見つけられていません...

地道に1つずつ手動で消していってもいいのですが、今回コンテナが7つもあるので、そういった初期設定済みのFRRイメージを作成するDockerfileを作りました。これを使って作った独自のFRRイメージを使って環境構築していきたいと思います。ついでにtcpdumpもインストールしています。
(tcpdumpに関してはホストOS側でvethを特定して...でもいいですが)

初期設定済みのFRRoutingイメージを作る

適当なフォルダにDockerfileを作ります。

Dockerfile
FROM frrouting/frr:v7.5.0
RUN apk update && apk add tcpdump
COPY docker-start /usr/lib/frr/docker-start

もともとのFRRoutingのイメージに、tcpdumpをインストールし、かつヘルパースクリプトを独自のものに置き換えています。

同じディレクトリにdocker-startという名前でヘルパースクリプトを作成。

docker-start
#!/bin/sh

set -e

# Delete all IP addresses on ethX
devlist=`ip -o addr | cut -d\  -f 2 | sed '1d'`
for dev in $devlist
do
  IP=`ip -o addr show $dev | cut -d\  -f 7`
  ip addr del $IP dev $dev
done

# Delete all routes
routelist=`ip route | cut -d\  -f 1`
for route in $routelist
do
  ip route del $route
done


##
# For volume mounts...
##
chown -R frr:frr /etc/frr || true
/usr/lib/frr/frrinit.sh start

# Sleep forever
exec tail -f /dev/null

これは、もともとFRRoutingで使われているスクリプトに、IPアドレスとルート全削除の処理を加えたものです。

スクリプトに実行権限を与えて、docker build

# chmod +x docker-start
# docker build -t frr .
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
frr                 latest              42eb8fb2ebb3        4 seconds ago       128MB

以降、コンテナ作成の際はfrrouting/frr:v7.5.0ではなく、frrを使っていきます。

ネットワークの作成

まずnetworkを作ります。

# docker network create net1 --subnet=10.1.1.0/24
# docker network create net2 --subnet=10.1.2.0/24
# docker network create net3 --subnet=172.16.1.0/24
# docker network create net4 --subnet=172.16.2.0/24
# docker network create net5 --subnet=172.16.3.0/24
# docker network create net6 --subnet=172.16.4.0/24
# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4f627b43f43a   bridge    bridge    local
62dced673495   host      host      local
b6c924d827f5   net1      bridge    local
ac750081ae13   net2      bridge    local
f96230418508   net3      bridge    local
9553456d0ad7   net4      bridge    local
5c39ee2ea4d7   net5      bridge    local
f2e362f4eaad   net6      bridge    local
20f96b0038a3   none      null      local

Pルータの作成

名前が1文字のコンテナは作れないようだったので、コンテナ名、ホスト名はPRとしました。

# docker create -it --name PR --hostname PR --privileged --net net1 frr
# docker network connect net2 PR
# docker start PR

いきなりdocker runするのではなく、docker create docker network connectでネットワークを全部つないでからコンテナを起動します。

PEルータの作成

# docker create -it --name PE1 --hostname PE1 --privileged --net net1 frr
# docker network connect net3 PE1
# docker network connect net5 PE1
# docker start PE1

# docker create -it --name PE2 --hostname PE2 --privileged --net net2 frr
# docker network connect net4 PE2
# docker network connect net6 PE2
# docker start PE2

CEルータの作成

# docker run -dit --name CE1 --hostname CE1 --privileged --net net3 frr
# docker run -dit --name CE2 --hostname CE2 --privileged --net net4 frr
# docker run -dit --name CE3 --hostname CE3 --privileged --net net5 frr
# docker run -dit --name CE4 --hostname CE4 --privileged --net net6 frr

動作確認

# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
01c534d3bafa   frr       "/sbin/tini -- /usr/…"   5 seconds ago    Up 3 seconds              CE4
861ce203d498   frr       "/sbin/tini -- /usr/…"   20 seconds ago   Up 19 seconds             CE3
acd7834f981b   frr       "/sbin/tini -- /usr/…"   32 seconds ago   Up 31 seconds             CE2
5ce59162b01b   frr       "/sbin/tini -- /usr/…"   56 seconds ago   Up 54 seconds             CE1
99bb821a4cb8   frr       "/sbin/tini -- /usr/…"   4 minutes ago    Up 3 minutes              PE2
824e0a5a5337   frr       "/sbin/tini -- /usr/…"   5 minutes ago    Up 5 minutes              PE1
aeaab8e9b887   frr       "/sbin/tini -- /usr/…"   6 minutes ago    Up 6 minutes              PR

コンテナが7つ起動していることを確認します。

3. FRR Daemonの設定

FRRの設定ファイルで、起動するdaemonを指定します。/etc/frr/daemonsを編集し、起動するdaemonをyesにしていきます。

各ルータにおいて(追加で)起動するdaemonは以下の通り。

ルータ種別 起動するdaemon
Pルータ ospfd, ldpd
PEルータ bgpd, ospfd, ldpd
CEルータ -

今回、CEルータとPEルータの間はStaticルーティングを使うため、CEルータでは特に追加でdaemonを起動する必要はありません。

Pルータ

/etc/frr/daemons
(略)
ospfd=yes
(略)
ldpd=yes
(略)

PEルータ

/etc/frr/daemons
(略)
bgpd=yes
(略)
ospfd=yes
(略)
ldpd=yes
(略)

それぞれ、設定ファイルの編集が終わったら設定反映のため、FRRのプロセスを再起動します。

Pルータの例:

PR
/ # /usr/lib/frr/frrinit.sh restart
Stopped watchfrr
Cannot stop ldpd: pid file not found
Cannot stop ospfd: pid file not found
Stopped staticdStopped zebra

Started watchfrr

Cannot stop ldpdとCannot stop ospfdが出ていますが、初回なので問題ありません。

動作確認

各コンテナ内で、指定したプロセスが立ち上がっているか確認します。
Pルータの例:

PR
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- /usr/lib/frr/docker-start
    7 root      0:00 tail -f /dev/null
   56 root      0:00 /bin/sh
   88 root      0:00 /usr/lib/frr/watchfrr -d -F traditional zebra ospfd ldpd staticd
  106 frr       0:00 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
  111 frr       0:00 /usr/lib/frr/ospfd -d -F traditional -A 127.0.0.1
  114 frr       0:00 /usr/lib/frr/ldpd -L -u frr -g frr
  115 frr       0:00 /usr/lib/frr/ldpd -E -u frr -g frr
  116 frr       0:00 /usr/lib/frr/ldpd -d -F traditional -A 127.0.0.1
  120 frr       0:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
  122 root      0:00 ps

ospfdとldpdが立ち上がっています。

4. OSPFの設定

まずはMPLSコア網の土台となるアンダーレイネットワークを作っていきます。今回はIGPとしてOSPFを使用。また、各ルータのループバックIFとして、dummy IFを作るやり方のページもありますが、本記事では各コンテナに最初からあるloを活用していきます。

Pルータ

PR(vtysh)
PR# conf
PR(config)# interface lo
PR(config-if)# ip address 9.9.9.9/32
PR(config-if)# exit

PR(config)# interface eth0
PR(config-if)# ip address 10.1.1.254/24
PR(config-if)# exit

PR(config)# interface eth1
PR(config-if)# ip address 10.1.2.254/24
PR(config-if)# exit

PR(config)# router ospf
PR(config-router)# network 9.9.9.9/32 area 0
PR(config-router)# network 10.1.1.0/24 area 0
PR(config-router)# network 10.1.2.0/24 area 0
PR(config-router)# end

PEルータ

PE1(vtysh)
PE1# conf
PE1(config)# interface lo
PE1(config-if)# ip address 1.1.1.1/32
PE1(config-if)# exit

PE1(config)# interface eth0
PE1(config-if)# ip address 10.1.1.2/24
PE1(config-if)# exit

PE1(config)# interface eth1
PE1(config-if)# ip address 172.16.1.254/24
PE1(config-if)# exit

PE1(config)# interface eth2
PE1(config-if)# ip address 172.16.3.254/24
PE1(config-if)# exit

PE1(config)# router ospf
PE1(config-router)# network 1.1.1.1/32 area 0
PE1(config-router)# network 10.1.1.0/24 area 0
PE1(config-router)# end
PE2(vtysh)
PE1# conf
PE1(config)# interface lo
PE1(config-if)# ip address 2.2.2.2/32
PE1(config-if)# exit

PE1(config)# interface eth0
PE1(config-if)# ip address 10.1.2.2/24
PE1(config-if)# exit

PE1(config)# interface eth1
PE1(config-if)# ip address 172.16.2.254/24
PE1(config-if)# exit

PE1(config)# interface eth2
PE1(config-if)# ip address 172.16.4.254/24
PE1(config-if)# exit

PE1(config)# router ospf
PE1(config-router)# network 2.2.2.2/32 area 0
PE1(config-router)# network 10.1.2.0/24 area 0
PE1(config-router)# end

動作確認

Pルータで確認してみます。

PR(vtysh)
PR# show ip ospf neighbor

Neighbor ID     Pri State           Dead Time Address         Interface                        RXmtL RqstL DBsmL
1.1.1.1           1 Full/Backup       36.732s 10.1.1.2        eth0:10.1.1.254                      0     0     0
2.2.2.2           1 Full/Backup       34.071s 10.1.2.2        eth1:10.1.2.254                      0     0     0

1.1.1.1, 2.2.2.2とneighborが確立できていることを確認。

PR(vtysh)
PR# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

O>* 1.1.1.1/32 [110/10] via 10.1.1.2, eth0, weight 1, 00:04:48
O>* 2.2.2.2/32 [110/10] via 10.1.2.2, eth1, weight 1, 00:01:19
O   9.9.9.9/32 [110/0] is directly connected, lo, weight 1, 00:09:18
C>* 9.9.9.9/32 is directly connected, lo, 00:12:31
O   10.1.1.0/24 [110/10] is directly connected, eth0, weight 1, 00:09:08
C>* 10.1.1.0/24 is directly connected, eth0, 00:12:00
O   10.1.2.0/24 [110/10] is directly connected, eth1, weight 1, 00:08:45
C>* 10.1.2.0/24 is directly connected, eth1, 00:11:39

1.1.1.1/32, 2.2.2.2/32へのルートがOSPFによって学習できており、FIBに載っていること(*がついていること)を確認します。

念のためpingも。vtyshから抜けて確認。

PR
/ # ping -I 9.9.9.9 1.1.1.1
PING 1.1.1.1 (1.1.1.1) from 9.9.9.9: 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=64 time=0.093 ms
64 bytes from 1.1.1.1: seq=1 ttl=64 time=0.488 ms
64 bytes from 1.1.1.1: seq=2 ttl=64 time=0.220 ms
^C
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.093/0.267/0.488 ms

/ # ping -I 9.9.9.9 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 9.9.9.9: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=64 time=0.140 ms
64 bytes from 2.2.2.2: seq=1 ttl=64 time=0.340 ms
64 bytes from 2.2.2.2: seq=2 ttl=64 time=0.486 ms
^C
--- 2.2.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.140/0.322/0.486 ms

通りました。

ちなみに、この時点でPE1(1.1.1.1)→PE2(2.2.2.2)にpingを打つと、当然ですが普通のIPパケットとして転送されます。PルータでtcpdumpしながらPE1→PE2にpingしてみます。

PE1
# ping -I 1.1.1.1 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 1.1.1.1: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=63 time=0.146 ms
64 bytes from 2.2.2.2: seq=1 ttl=63 time=0.374 ms
64 bytes from 2.2.2.2: seq=2 ttl=63 time=0.373 ms
^C
--- 2.2.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.146/0.297/0.374 ms
PR
/ # tcpdump -n -i any icmp
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
06:51:43.879037 eth0  In  IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 44032, seq 0, length 64
06:51:43.879052 eth1  Out IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 44032, seq 0, length 64
06:51:43.879080 eth1  In  IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 44032, seq 0, length 64
06:51:43.879084 eth0  Out IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 44032, seq 0, length 64

5. LDPの設定

MPLSの設定に入っていきます。公式docsに記載の通り、事前準備としてカーネルパラメータを変更してMPLS転送を有効にする必要があります。各ルータでカーネルパラメータ設定→LDP設定の順で進めます。

Pルータ

MPLSの有効化

カーネルパラメータを変更してMPLSを有効にします。
まずは状態確認。

PR
/ # sysctl -a | grep mpls
sysctl: error reading key 'net.ipv6.conf.all.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.default.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth0.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth1.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.lo.stable_secret': I/O error
net.mpls.conf.eth0.input = 0
net.mpls.conf.eth1.input = 0
net.mpls.conf.lo.input = 0
net.mpls.default_ttl = 255
net.mpls.ip_ttl_propagate = 1
net.mpls.platform_labels = 0

ipv6関連のエラーが出ていますが、一旦気にしないで進めます。

Pルータではeth0, eth1の両方でMPLS転送を有効にします。net.mpls.conf.eth0.inputnet.mpls.conf.eth1.inputを1に、net.mpls.platform_labelsを100000に設定するため、下記のファイルに追記。

/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.platform_labels = 100000

設定を反映します。

/ # sysctl -p
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.platform_labels = 100000
# sysctl -a | grep mpls
sysctl: error reading key 'net.ipv6.conf.all.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.default.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth0.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth1.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.lo.stable_secret': I/O error
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.conf.lo.input = 0
net.mpls.default_ttl = 255
net.mpls.ip_ttl_propagate = 1
net.mpls.platform_labels = 100000

値が反映されました。

LDPの設定

MPLSのラベル交換のため、LDPを動かします。

PR(vtysh)
PR# conf
PR(config)# mpls ldp
PR(config-ldp)# address-family ipv4
PR(config-ldp-af)# discovery transport-address 9.9.9.9
PR(config-ldp-af)# interface eth0
PR(config-ldp-af-if)# exit
PR(config-ldp-af)# interface eth1
PR(config-ldp-af-if)# exit
PR(config-ldp-af)# end

MPLS網に参加するインターフェースだけ登録します。(ループバックは不要)

PEルータ

PE1, PE2も同様にカーネルパラメータの設定とLDPの設定をします。

PE1

/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.platform_labels = 100000
vtysh
PE1# conf
PE1(config)# mpls ldp
PE1(config-ldp)# address-family ipv4
PE1(config-ldp-af)# discovery transport-address 1.1.1.1
PE1(config-ldp-af)# interface eth0
PE1(config-ldp-af-if)# exit
PE1(config-ldp-af)# end

PE2

/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.platform_labels = 100000
vtysh
PE2# conf
PE2(config)# mpls ldp
PE2(config-ldp)# address-family ipv4
PE2(config-ldp-af)# discovery transport-address 2.2.2.2
PE2(config-ldp-af)# interface eth0
PE2(config-ldp-af-if)# exit
PE2(config-ldp-af)# end

動作確認

Pルータで確認します。

PR(vtysh)
PR# show mpls ldp neighbor
AF   ID              State       Remote Address    Uptime
ipv4 1.1.1.1         OPERATIONAL 1.1.1.1         00:04:41
ipv4 2.2.2.2         OPERATIONAL 2.2.2.2         00:00:50

LDPのneighborが確立できています。

PR(vtysh)
PR# show mpls ldp binding
AF   Destination          Nexthop         Local Label Remote Label  In Use
ipv4 1.1.1.1/32           1.1.1.1         16          imp-null         yes
ipv4 1.1.1.1/32           2.2.2.2         16          16                no
ipv4 2.2.2.2/32           1.1.1.1         17          16                no
ipv4 2.2.2.2/32           2.2.2.2         17          imp-null         yes
ipv4 9.9.9.9/32           1.1.1.1         imp-null    17                no
ipv4 9.9.9.9/32           2.2.2.2         imp-null    17                no
ipv4 10.1.1.0/24          1.1.1.1         imp-null    imp-null          no
ipv4 10.1.1.0/24          2.2.2.2         imp-null    18                no
ipv4 10.1.2.0/24          1.1.1.1         imp-null    18                no
ipv4 10.1.2.0/24          2.2.2.2         imp-null    imp-null          no
ipv4 172.16.1.0/24        1.1.1.1         -           imp-null          no
ipv4 172.16.2.0/24        2.2.2.2         -           imp-null          no
ipv4 172.16.3.0/24        1.1.1.1         -           imp-null          no
ipv4 172.16.4.0/24        2.2.2.2         -           imp-null          no

「LIBテーブル」の情報が確認できます。

PR(vtysh)
PR# show mpls table
 Inbound Label  Type  Nexthop   Outbound Label
 -----------------------------------------------
 16             LDP   10.1.2.2  implicit-null
 17             LDP   10.1.1.2  implicit-null

こちらは「LFIBテーブル」ですね。

先ほどと同様、PE1(1.1.1.1)→PE2(2.2.2.2)にpingを打ってみます。

PE1
/ # ping -c 1 -I 1.1.1.1 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 1.1.1.1: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=63 time=0.121 ms

--- 2.2.2.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.121/0.121/0.121 ms

この時、Pルータでパケットキャプチャをすると、生のIPパケットではなく、MPLSラベルのついたパケットが転送されていることがわかります。

PR
/ # tcpdump -n -i any -l | grep ICMP
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
07:17:28.103630 eth0  In  MPLS (label 17, exp 0, [S], ttl 64) IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 51200, seq 0, length 64
07:17:28.103636 eth1  Out IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 51200, seq 0, length 64
07:17:28.103668 eth1  In  MPLS (label 16, exp 0, [S], ttl 64) IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 51200, seq 0, length 64
07:17:28.103669 eth0  Out IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 51200, seq 0, length 64

行きはlabel 17が付けられたパケットがPルータのeth0から入り、PルータのLFIBテーブル(Label 17に対してimplicit-null)に従ってラベルが剥がされ、生のIPパケットになってeth1から出ていく様子がわかります。帰りはlabel 16で同様の処理が行われています。

6. MP-BGPの設定

PEルータでMP-BGPの設定をします。

まずPE間でiBGPピアを張る設定と、VPN-IPv4のアドレス情報を運ぶためのAddress Familyの設定です。

PE1(vtysh)
PE1# conf
PE1(config)# router bgp 65000
PE1(config-router)# neighbor 2.2.2.2 remote-as 65000
PE1(config-router)# neighbor 2.2.2.2 update-source 1.1.1.1
PE1(config-router)# address-family ipv4 vpn
PE1(config-router-af)# neighbor 2.2.2.2 activate
PE1(config-router-af)# exit-address-family
PE1(config-router)# end

PE2も同様に設定します。

PE2(vtysh)
PE2# conf
PE2(config)# router bgp 65000
PE2(config-router)# neighbor 1.1.1.1 remote-as 65000
PE2(config-router)# neighbor 1.1.1.1 update-source 2.2.2.2
PE2(config-router)# address-family ipv4 vpn
PE2(config-router-af)# neighbor 1.1.1.1 activate
PE2(config-router-af)# exit-address-family
PE2(config-router)# end

動作確認

PE1(vtysh)
PE1# show ip bgp summary

IPv4 Unicast Summary:
BGP router identifier 1.1.1.1, local AS number 65000 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 1, using 14 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
2.2.2.2         4      65000        13        16        0    0    0 00:01:16            0        0

Total number of neighbors 1

IPv4 VPN Summary:
BGP router identifier 1.1.1.1, local AS number 65000 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 1, using 14 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
2.2.2.2         4      65000        13        16        0    0    0 00:01:16            0        0

Total number of neighbors 1

Up/Downの欄がneverになっていないことを確認します。

VPN-IPv4のBGPテーブルも見てみます。

PE1(vtysh)
PE1# show bgp ipv4 vpn
No BGP prefixes displayed, 0 exist

この時点ではまだルートを学習していないので、エントリーはありません。

7. VRFの設定

PE1/PE2で各ユーザごとのVRFを作り、ユーザルートの設定とルート再配送の設定をします。

VRFの作成

各CEルータを収容するためのVRFを作成します。Linux VRFの機能を使っていきます。VRF作成はFRRoutingでできないので、ipコマンドを使って作ります。また、作ったVRFに、対応するIFを所属させます。A社用のVRF名はCUSTA、B社用はCUSTBとします。

PE1
/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
41: eth2@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:10:03:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:01:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
45: eth1@if46: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:10:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0

/ # ip link add CUSTA type vrf table 10
/ # ip link add CUSTB type vrf table 20
/ # ip link set CUSTA up
/ # ip link set CUSTB up
/ # ip link set eth1 master CUSTA
/ # ip link set eth2 master CUSTB

/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: CUSTA: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 12:1b:1d:12:01:7f brd ff:ff:ff:ff:ff:ff
3: CUSTB: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether de:06:68:aa:a5:ec brd ff:ff:ff:ff:ff:ff
41: eth2@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTB state UP mode DEFAULT group default
    link/ether 02:42:ac:10:03:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:01:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
45: eth1@if46: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTA state UP mode DEFAULT group default
    link/ether 02:42:ac:10:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0

vtyshで確認できます。

PE1(vtysh)
PE1# show int br
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTA           up      CUSTA
eth1            up      CUSTA           172.16.1.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTB           up      CUSTB
eth2            up      CUSTB           172.16.3.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
eth0            up      default         10.1.1.2/24
lo              up      default         1.1.1.1/32

PE2でも同様に作成します。

PE2
/ # ip link add CUSTA type vrf table 10
/ # ip link add CUSTB type vrf table 20
/ # ip link set CUSTA up
/ # ip link set CUSTB up
/ # ip link set eth1 master CUSTA
/ # ip link set eth2 master CUSTB

/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: CUSTA: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:48:e9:21:1e:6b brd ff:ff:ff:ff:ff:ff
3: CUSTB: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 16:8f:42:1f:9b:84 brd ff:ff:ff:ff:ff:ff
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:02:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
49: eth1@if50: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTA state UP mode DEFAULT group default
    link/ether 02:42:ac:10:02:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
51: eth2@if52: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTB state UP mode DEFAULT group default
    link/ether 02:42:ac:10:04:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
PE2(vtysh)
PE2# show int br
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTA           up      CUSTA
eth1            up      CUSTA           172.16.2.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTB           up      CUSTB
eth2            up      CUSTB           172.16.4.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
eth0            up      default         10.1.2.2/24
lo              up      default         2.2.2.2/32

ユーザ向けルートの設定

今回CE-PE間はスタティックルーティングを使います。VRF CUSTA, CUSTBそれぞれで設定。

PE1(vtysh)
PE1# conf
PE1(config)# ip route 192.168.1.0/24 172.16.1.2 vrf CUSTA
PE1(config)# ip route 192.168.3.0/24 172.16.3.2 vrf CUSTB
PE1(config)# exit

PE1# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.1.0/24 is directly connected, eth1, 04:55:40
S>* 192.168.1.0/24 [1/0] via 172.16.1.2, eth1, weight 1, 04:12:14

PE1# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.3.0/24 is directly connected, eth2, 04:55:37
S>* 192.168.3.0/24 [1/0] via 172.16.3.2, eth2, weight 1, 04:09:52

PE2も同様に設定。

PE2(vtysh)
PE2# conf
PE2(config)# ip route 192.168.2.0/24 172.16.2.2 vrf CUSTA
PE2(config)# ip route 192.168.4.0/24 172.16.4.2 vrf CUSTB
PE2(config)# exit

PE2# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.2.0/24 is directly connected, eth1, 05:00:25
S>* 192.168.2.0/24 [1/0] via 172.16.2.2, eth1, weight 1, 04:17:02

PE2# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.4.0/24 is directly connected, eth2, 05:00:26
S>* 192.168.4.0/24 [1/0] via 172.16.4.2, eth2, weight 1, 04:16:34

ルート再配送の設定

VRFのルートテーブルと「グローバルルーティングテーブル」の間の、ルート再配送の設定。

PE1(vtysh)
PE1# conf
PE1(config)# router bgp 65000 vrf CUSTA
PE1(config-router)# address-family ipv4 unicast
PE1(config-router-af)# redistribute static
PE1(config-router-af)# label vpn export auto
PE1(config-router-af)# rd vpn export 1:100
PE1(config-router-af)# rt vpn both 10:100
PE1(config-router-af)# export vpn
PE1(config-router-af)# import vpn
PE1(config-router-af)# exit-address-family
PE1(config-router)# exit

PE1(config)# router bgp 65000 vrf CUSTB
PE1(config-router)# address-family ipv4 unicast
PE1(config-router-af)# redistribute static
PE1(config-router-af)# label vpn export auto
PE1(config-router-af)# rd vpn export 2:100
PE1(config-router-af)# rt vpn both 20:100
PE1(config-router-af)# export vpn
PE1(config-router-af)# import vpn
PE1(config-router-af)# exit-address-family
PE1(config-router)# end

この辺り、Ciscoと設定方法が若干違うようですが、やっていることとしては以下のように理解しています。

  • label vpn export auto:VRFからグローバルルーティングテーブルに対してルートをexportする時に、MPLSのラベルを自動でつける
  • export|import vpn:VRFとグローバルルーティングテーブル間でルートのimport/exportを可能にする

  • VRF内のルート情報(address-family = ipv4 unicast)をグローバルルーティングテーブル側(address-family = VPNv4: RD/RT等の情報が付与されたアドレス情報)に配送する際の設定として、「MPLSのラベル付け」「RD/RT情報の付与」「そもそもVRF内のルート⇔グローバルルーティングテーブルの情報のやりとりをONにする」という設定を行っている。

また、今回CE-PE間はStaticルーティングでの設定としているため、redistribute staticとしています。

PE2も同様に設定。

PE2(vtysh)
PE2# conf
PE2(config)# router bgp 65000 vrf CUSTA
PE2(config-router)# address-family ipv4 unicast
PE2(config-router-af)# redistribute static
PE2(config-router-af)# label vpn export auto
PE2(config-router-af)# rd vpn export 1:100
PE2(config-router-af)# rt vpn both 10:100
PE2(config-router-af)# export vpn
PE2(config-router-af)# import vpn
PE2(config-router-af)# exit-address-family
PE2(config-router)# exit

PE2(config)# router bgp 65000 vrf CUSTB
PE2(config-router)# address-family ipv4 unicast
PE2(config-router-af)# redistribute static
PE2(config-router-af)# label vpn export auto
PE2(config-router-af)# rd vpn export 2:100
PE2(config-router-af)# rt vpn both 20:100
PE2(config-router-af)# export vpn
PE2(config-router-af)# import vpn
PE2(config-router-af)# exit-address-family
PE2(config-router)# end

これを設定すると、BGPでユーザルートが学習されます。

動作確認

PE1で確認。まずはBGPテーブル(VPN-IPv4ルート)を見てみます。

PE1
PE1# show ip bgp ipv4 vpn
BGP table version is 6, local router ID is 1.1.1.1, vrf id 0
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
Route Distinguisher: 1:100
*> 192.168.1.0/24   172.16.1.2@2<            0         32768 ?
    UN=172.16.1.2 EC{10:100} label=146 type=bgp, subtype=5
*>i192.168.2.0/24   2.2.2.2                  0    100      0 ?
    UN=2.2.2.2 EC{10:100} label=146 type=bgp, subtype=0
Route Distinguisher: 2:100
*> 192.168.3.0/24   172.16.3.2@3<            0         32768 ?
    UN=172.16.3.2 EC{20:100} label=147 type=bgp, subtype=5
*>i192.168.4.0/24   2.2.2.2                  0    100      0 ?
    UN=2.2.2.2 EC{20:100} label=147 type=bgp, subtype=0

Displayed  4 routes and 4 total paths

ユーザルートが載りました。PE2で設定した192.168.2.0/24と192.168.4.0/24もiBGPで学習されています。先ほど設定したRD/RT情報も載っています。

次に、VRFごとのBGPテーブル。

PE1# show ip bgp vrf CUSTA
BGP table version is 2, local router ID is 172.16.1.254, vrf id 2
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 192.168.1.0/24   172.16.1.2               0         32768 ?
*> 192.168.2.0/24   2.2.2.2@0<               0    100      0 ?

Displayed  2 routes and 2 total paths
PE1# show ip bgp vrf CUSTB
BGP table version is 4, local router ID is 172.16.3.254, vrf id 3
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 192.168.3.0/24   172.16.3.2               0         32768 ?
*> 192.168.4.0/24   2.2.2.2@0<               0    100      0 ?

Displayed  2 routes and 2 total paths

それぞれユーザルートが正しく学習されています。
次に、VRFごとのルートテーブルを見てみます。

PE1# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.1.0/24 is directly connected, eth1, 05:29:08
S>* 192.168.1.0/24 [1/0] via 172.16.1.2, eth1, weight 1, 04:45:42
B>  192.168.2.0/24 [200/0] via 2.2.2.2 (vrf default) (recursive), label 146, weight 1, 00:04:28
  *                          via 10.1.1.254, eth0 (vrf default), label 17/146, weight 1, 00:04:28
PE1# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.3.0/24 is directly connected, eth2, 05:29:07
S>* 192.168.3.0/24 [1/0] via 172.16.3.2, eth2, weight 1, 04:43:22
B>  192.168.4.0/24 [200/0] via 2.2.2.2 (vrf default) (recursive), label 147, weight 1, 00:16:20
  *                          via 10.1.1.254, eth0 (vrf default), label 17/147, weight 1, 00:16:20

それぞれ、BGPテーブルを介して、PE2から受信したユーザルートが載っていることがわかります。

8. CEルータの設定

最後に、CEの設定をしていきます。
ループバックアドレス、PE向かいのアドレス、デフォルトルートを設定します。

CE1(vtysh)
CE1# conf
CE1(config)# interface lo
CE1(config-if)# ip address 192.168.1.1/24
CE1(config-if)# exit
CE1(config)# interface eth0
CE1(config-if)# ip address 172.16.1.2/24
CE1(config-if)# exit
CE1(config)# ip route 0.0.0.0/0 172.16.1.254
CE1(config)# end

CE2~4も同様。

CE2(vtysh)
CE2# conf
CE2(config)# interface lo
CE2(config-if)# ip address 192.168.2.1/24
CE2(config-if)# exit
CE2(config)# interface eth0
CE2(config-if)# ip address 172.16.2.2/24
CE2(config-if)# exit
CE2(config)# ip route 0.0.0.0/0 172.16.2.254
CE2(config)# end
CE3(vtysh)
CE3# conf
CE3(config)# interface lo
CE3(config-if)# ip address 192.168.3.1/24
CE3(config-if)# exit
CE3(config)# interface eth0
CE3(config-if)# ip address 172.16.3.2/24
CE3(config-if)# exit
CE3(config)# ip route 0.0.0.0/0 172.16.3.254
CE3(config)# end
CE4(vtysh)
CE4# conf
CE4(config)# interface lo
CE4(config-if)# ip address 192.168.4.1/24
CE4(config-if)# exit
CE4(config)# interface eth0
CE4(config-if)# ip address 172.16.4.2/24
CE4(config-if)# exit
CE4(config)# ip route 0.0.0.0/0 172.16.4.254
CE4(config)# end

動作確認

これですべて完成したはずです。
CE1→CE2にpingが通ることを確認します。-IでSourceとしてループバックIFを指定。

CE1
/ # ping -I 192.168.1.1 192.168.2.1
PING 192.168.2.1 (192.168.2.1) from 192.168.1.1: 56 data bytes
64 bytes from 192.168.2.1: seq=0 ttl=62 time=0.650 ms
64 bytes from 192.168.2.1: seq=1 ttl=62 time=0.751 ms
64 bytes from 192.168.2.1: seq=2 ttl=62 time=0.675 ms
^C
--- 192.168.2.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.650/0.692/0.751 ms

通りました!

CE3→CE4にpingが通ることを確認します。

CE3
/ # ping -I 192.168.3.1 192.168.4.1
PING 192.168.4.1 (192.168.4.1) from 192.168.3.1: 56 data bytes
64 bytes from 192.168.4.1: seq=0 ttl=62 time=0.470 ms
64 bytes from 192.168.4.1: seq=1 ttl=62 time=0.157 ms
64 bytes from 192.168.4.1: seq=2 ttl=62 time=0.777 ms
^C
--- 192.168.4.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.157/0.468/0.777 ms

通りました!

試しに、CE1→CE4には通らないことを確認します。

CE1
/ # ping -I 192.168.1.1 192.168.4.1
PING 192.168.4.1 (192.168.4.1) from 192.168.1.1: 56 data bytes
^C
--- 192.168.4.1 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

通らないので成功です!

パケットキャプチャしてみる

上記CE1→CE2のpingの際、PE1のeth1,0, Pルータのeth0,1, PE2のeth0,1でパケットキャプチャすると、ラベルがついたり取られたりしながらパケットが転送される様子が見えます。

PE1/PR/PE2
/ # tcpdump -n -i any -l | grep ICMP
CE1
/ # ping -c 1 -I 192.168.1.1 192.168.2.1
PING 192.168.2.1 (192.168.2.1) from 192.168.1.1: 56 data bytes
64 bytes from 192.168.2.1: seq=0 ttl=62 time=0.551 ms

--- 192.168.2.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.551/0.551/0.551 ms

結果を時系列で並べると以下のようになりました。

行き:

PE1
08:13:50.550878 eth1  In  IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550900 eth0  Out MPLS (label 17, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
PR
08:13:50.550905 eth0  In  MPLS (label 17, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550909 eth1  Out MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
PE2
08:13:50.550913 eth0  In  MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550916 CUSTA Out IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550926 eth1  Out IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64

PE1のeth0に届くのは生のIPパケットですが、そこで内側のラベル146(VPN識別用)と外側のラベル17(MPLS網内転送用)がつけられeth1から出ていきます。PRではLFIBテーブル(Label 17に対してはimplicit null)にしたがって外側のラベルが剥がされた状態でPE2に転送されています。PE2では内側のラベルも剥がされ、VRF CUSTAのルーティングテーブルに従って生のIPパケットとしてCE2に転送されています。 最後Outが2つあるのはVRF CUSTAとeth1の分ですね。3

帰り:
帰りも同様です。内側のVPN識別用ラベルとしては146が割り当てられています。

PE2
08:13:50.550976 eth1  In  IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550981 eth0  Out MPLS (label 16, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
PR
08:13:50.550984 eth1  In  MPLS (label 16, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550986 eth0  Out MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
PE1
08:13:50.550988 eth0  In  MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550990 CUSTA Out IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550994 eth1  Out IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64

参考


  1. https://github.com/moby/moby/issues/25181 

  2. https://github.com/moby/libnetwork/issues/2093 

  3. tcpdumpのIF指定を-i anyにすると、VRF CUSTAへのINが表示されないようです。個別にIF指定するとどちらも表示されるのですが... なぜだろう... 

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

Ruby on Rails 初心者向け!!投稿一覧・詳細表示機能まとめ

はじめに

 今は業務で主にphpを使っているのですが、今回自主学習でRailsを使用して何か成果物を作成したいと思い学習をはじめました。何かアドバイスや修正箇所があればコメントよろしくお願いします。

開発環境

  • Docker
  • ruby 2.3.7
  • Rails 5.2.4.4
  • MySQL 5.7.32

前提

 Railsの開発環境の構築を行い、新規投稿機能の実装まで完了が終わっていることが前提です。
新規投稿機能は前回の記事を参考に実装してみてください。
Ruby on Rails 初心者向け!!新規投稿機能まとめ

投稿一覧表示機能実装

コントローラーにアクションを追加

まずは投稿一覧機能を実装していきます。一覧表示機能は基本的にはindexアクションを使います。
前回作成した、postコントローラーにindexアクションを追加していきます。

post.controller.rb
 def index

 end

ルーティング設定

次にルーティング設定です。

config/routes.rbに下記の記述を追加します。

routes.rb
Rails.application.routes.draw do
  get 'posts/new' => 'posts#new'
  # ここから
  get 'posts'     => 'posts#index'
  # ここまで追加
  post 'posts'    => 'posts#create'
end

これで「GETメソッドでpostsパスにアクセスするとpostsコントローラーのindexアクションにアクセスする」という設定ができます。

ターミナルからも設定を確かめてみましょう。
以下のコマンドでルーティングの設定を確認できます。

# rails routes
posts GET  /posts(.:format)  posts#index                                                               

これでルーティング設定は完了です。

コントローラー設定

次はindexアクションの中身は書いていきます。

posts_controller.rb
def index
    @posts = Post.all
end

投稿一覧情報を取得するには、postモデルに対してallメソッドを使います。これでインスタンス変数@postsの中には、投稿されたデータ一覧が配列で格納されます。代入する変数名は複数形にしておきます。

ビュー設定

次はビューで先ほど取得した投稿データの表示を行います。
まずは、index.html.erbファイルを新規作成し、中身は書いていきます。

index.html.erb
<h1>投稿一覧</h1>
<% @posts.each do |post| %>
  <p>タイトル</p>
  <span><%= post.title %></span>
<% end %>

<% @posts.each do |post| %>は、繰り返し処理で、先ほどコントローラーで定義した投稿一覧が格納されたインスタンス変数@postsを一つずつ取り出して、ブロック変数のpostに格納しているという意味です。
これにより、each文の中で投稿の一つ一つが格納されたブロック変数postが使用できるようになります。

<%= post.title %>の記述は、ブロック変数postのタイトルカラムを一つずつ表示しています。「変数名.カラム名」で表示するカラムが指定できます。

これで「/posts」のURLにアクセスすると投稿のタイトル一覧が表示されていると思います。
スクリーンショット 2021-01-11 14.57.33.png

リンクの作成

次に新規投稿画面から、投稿一覧画面に遷移するリンクを設定していきます。
リンクの実装には、link_toメソッドを使用します。
新規投稿画面のビューに以下を記述していきます。

# ここから
<span>
    <%= link_to "投稿一覧", "/posts" %>
</span>
# ここまで追加
<h1>投稿フォーム</h1>
<%= form_for(@post, url: '/posts') do |f| %>
  <h3>タイトル</h3>
  <%= f.text_field :title %>
  <h3>本文</h3>
  <%= f.text_area :body %>
  <%= f.submit '投稿' %>
<% end %>

linl_toメソッドは、

<%= link_to 表示させるテキスト , "リンク先URL" [,オプション] %>

のように使う記述します。

これで投稿一覧ページへの遷移するテキストが投稿フォームの上に表示されていると思います。

スクリーンショット 2021-01-11 15.27.40.png

これで投稿一覧表示機能の実装ができました。

投稿詳細表示機能実装

コントローラーにアクションを追加

次に投稿詳細機能の実装をしていきます。詳細表示機能にはshowアクションを使用します。
実装の流れは一覧表示機能と同じです。
ではpostsコントローラーにshowアクションを定義していきます。

posts_controller.rb
def show

end

ルーティング設定

次にルーティング設定です。

routes.rb
Rails.application.routes.draw do
  get 'posts/new' => 'posts#new'
  get 'posts'     => 'posts#index'
  # ここから
  get 'posts/:id' => 'postss#show'
 # ここまで追加
  post 'posts'    => 'posts#create'
end

詳細表示のURLはどの詳細データか識別するために末尾にidがつきます。
IDが1の投稿の場合は、「posts/1」のようなURLになります。
コロン(:)idで設定することができます。

コントローラー設定

showアクションの中身を記述していきます。

posts_controller.rb
def show
    @post = Post.find(params[:id])
end

アクション内にparams[:id]と記述することで、先ほどルーティングで設定したURLの末尾のIDを取得することができます。findメソッドをしようすることで、idが1の場合、「IDが1のレコードを一件取得する」という意味になります。

ビュー設定

次に詳細表示画面のビューを作成します。
show.html.erbを作成し、中身を編集します。

show.html.erb
<h2>タイトル</h2>
<p><%= @post.title %></p>
<h2>本文</h2>
<p><%= @post.body %></p>

showアクションの中に定義したインスタンス変数.カラム名でそのカラムのデータを表示することができます。今回はtitleとbodyを表示します。

これでURLが「posts/1」にアクセスすると以下のようにid=1のデータが表示されます。
スクリーンショット 2021-01-11 21.46.23.png

リンクの作成

最後に投稿一覧画面から、詳細画面へのリンクを設定していきます。
投稿一覧画面のviewを以下のうように書き換えます。

index.html.erb
<h1>投稿一覧</h1>
<% @posts.each do |post| %>
  <p>タイトル</p>
  <span><%= link_to post.title, "/post/#{post.id}" %></span>
<% end %>

これで一つ一つの投稿のタイトルをクリックするとその投稿の詳細ページへ遷移することができるようになりました。

スクリーンショット 2021-01-11 21.54.03.png

まとめ

  • 投稿一覧表示機能はindexアクション、詳細表示機能はshowアクションを使用する。
  • DBからデータ一覧を取得するにはモデルに対してallメソッドを使用する。特定のデータを取得するにはfindメソッドでidを指定して取得する。
  • 画面遷移のリンクを作成するには、link_toメソッドを使用する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘録:【Docker】起動&終了方法

備忘録φ(..)メモメモ

【起動方法】

アプリ起動

スクリーンショット 2021-01-11 21.20.54.png

Dockerの置いてる場所で下記を入力

※私の場合”dev”というディレクトリ内に入って入力

terminal
lancai@oja dev % docker-compose up -d

↓下記のように返ってきたらOK

terminal
Creating network "proxy-net" with driver "bridge"
Creating rootp-db ... done

↓RUNNINGとなっていたらOK

スクリーンショット 2021-01-11 21.24.21.png

終了方法

Dockerの置いてる場所で下記を入力

※私の場合”dev”というディレクトリ内に入って入力

terminal
$ lancai@oja dev % docker-compose down

↓下記のように返ってきたらOK

terminal
Stopping ※DBの名前 ... done
Removing ※DBの名前 ... done
Removing network proxy-net
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerでphpの設定

Dockerfile

Dockerfile.
FROM php:x.x-fpm

COPY ./php.ini /usr/local/etc/php/php.ini
COPY ./php-fpm.d/www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

まずphp.inizzz-www.confをコピーする。

php.iniの内容

の中でも意味のわかるものを載せます。(文字コードを省く)

php.ini
;エラー時にブラウザにエラー内容を表示(開発環境でならon)
desplay_errors = On 
;エラーログを吐く設定を有効に
log_errors = On
;phpのエラーログを /var/log/php_error.log に吐く設定
error_log = /var/log/php_error.log

;値は自由に
;メモリーの上限を256MBに
memory_limit = 256M
;POSTリクエストの受付る最大値
post_max_size = 128M
;アップロードファイルの受付上限
upload_max_filesize = 64M

;レスポンスヘッダにPHPのバージョンを記載しない
expose_php = Off
;セッションIDのハッシュアルゴリズムをSHA-1(160bit)に変更
session.hash_function = 1
;強制終了までの許容時間
max_execution_time = 30
;入力変数の最大許容数
max_input_vars = 1000

; PHPファイルのタグを<?php ?>のみ利用可能と制限(<? ?>などを使用不可にする)
short_open_tag = Off
;Time zone
date.timezone = 設定したいTZ

私はこの記事を参考にしました。参考記事
大体2,3個の記事を参考にしたら自力で書けそう。

zzz-www.confの内容

ファイル名がこれなのは公式イメージのzz-docker.confでlistenを上書きしてしまわないようにzz-docker.confの後に読ませたいので、後に読ませられればなんでもいい。

zzz-www.conf
listen = /var/run/php-fpm/php-fpm.sock
listen.group = www-data
listen.owner = www-data
listen.mode = 0666

1行目はソケットの位置を指定。(nginxの設定と合わせる)
4行目listen.modeはデフォルトのlisten.mode = 0660だとパーミッションエラーが起きるようで0666にした方がいい。
4行目の参考Nginx+PHP-FPMでUNIXドメインソケットを使っていてハマる

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

DockerでComposerをインストール

今後Dockerを触るときに役立つようにメモ用として。

DockerfileでRUNを使うかCOPYを使うか

Composerの公式ページからやっても良いけど、composer公式

Dockerfile.
FROM php:x.xx-fpm

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
    php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
    php composer-setup.php
    php -r "unlink('composer-setup.php');"

これは2行目のハッシュ値が原因でバージョンアップのたびにDockerfileを変更する...みたいな問題があるよう。

COPYを使う方がいい

Dockerfile.
FROM php:x.xx-fpm

COPY COPY --from=composer /usr/bin/composer /usr/bin/composer

これを記述するだけですむ。
composerのバージョンを指定したいときは
COPY --from=composer:xx.x /usr/bin/composer /usr/bin/composer

にすればいい。

ついでに

ENV COMPOSER_ALLOW_SUPERUSER=1 

を指定するとrootでのインストールを許可する。

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

【Rails】DockerからRails newができない

【Rails】Rails 6.0 x Docker x MySQLで環境構築

こちらの記事を参考に、
Rails6でDocker環境を構築中、
下記のエラーが出ました。

ターミナル
Bind for 0.0.0.0:3306 failed: port is already allocated

直訳するとすでに3306のポートは割り当てられてますよとのこと。

ターミナル
$ docker ps
ターミナル
CONTAINER ID   IMAGE       COMMAND                  CREATED       
fbfa25282c46   mysql:5.7   "docker-entrypoint.s…"   7 hours ago
STATUS       PORTS                               
Up 7 hours   0.0.0.0:3306->3306/tcp, 33060/tcp
ターミナル
$ docker stop fbfa25282c46

3306を発見!!

でコンテナをストップさせ、
無事にrails newができました。

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

Docker で Embulk を動かしてファイル変換

Docker コンテナで Embulk を動かせるようにし、テストとして csv ファイルを orc ファイルに変換してみる

Embulk ?

バッチ的にファイルやデータベースからデータを吸い出し、別のストレージやデータベースにロードできるツール
詳細は公式ドキュメントか、下記開発者の方のブログを参照ください

環境情報

Docker コンテナ内で、手動で Embulk をインストール

とりあえずコンテナ立ち上げて、手動で入れてみる

docker run -it --rm java:8 bash

公式ドキュメントにあるやり方で embulk をインストール

  • 後々イメージ化することを考えると$HOME 配下にパスを通すのが難しいので、実行ファイルのインストール場所は、/usr/local/binにする
  • なので、公式の手順内の ~/.embulk/bin/embulk の箇所は、 /usr/local/bin/embulkに書き換える
  • 手順内の PATH の更新の箇所も行わない
curl --create-dirs -o /usr/local/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar"
chmod +x /usr/local/bin/embulk

公式ドキュメントにあるクイックスタートに沿って実際に動かしてみる

embulk example ./try1
embulk guess ./try1/seed.yml -o config.yml
embulk run config.yml

embulk run の結果、以下のような結果が標準出力されればOK

1,32864,2015-01-27 19:23:49,20150127,embulk
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
3,27559,2015-01-28 02:20:02,20150128,Embulk "csv" parser plugin
4,11270,2015-01-29 11:54:36,20150129,

↑ で何をやったのか

  • try1/csv/sample_01.csv.gz の中身を、stdout (標準出力) に出力した

config.yml の中身

root@7e9764e79b83:/# cat config.yml
in:
  type: file
  path_prefix: /./try1/csv/sample_
  decoders:
  - {type: gzip}
  parser:
    charset: UTF-8
    newline: LF
    type: csv
    delimiter: ','
    quote: '"'
    escape: '"'
    null_string: 'NULL'
    trim_if_not_quoted: false
    skip_header_lines: 1
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: id, type: long}
    - {name: account, type: long}
    - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'}
    - {name: purchase, type: timestamp, format: '%Y%m%d'}
    - {name: comment, type: string}
out: {type: stdout}

try1/csv/sample_01.csv.gz の中身確認

root@7e9764e79b83:/# zcat try1/csv/sample_*
id,account,time,purchase,comment
1,32864,2015-01-27 19:23:49,20150127,embulk
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
4,11270,2015-01-29 11:54:36,20150129,NULL

Dockerfile作成 & コンテナ起動

動作確認が終わったので、上記手動手順を Dockerfile 化する

  • ENTRYPOINT で、/usr/local/vin/embulk を指定したかったが、ENTRYPOINT では jar を直接的に起動できないので、java -jarで起動
Dockerfile
FROM java:8

# Install embulk
RUN curl --create-dirs -o /usr/local/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar" &&\
    chmod +x /usr/local/bin/embulk

ENTRYPOINT ["java", "-jar", "/usr/local/bin/embulk"]

build

docker build -t embulk .

確認

docker run -it --rm embulk:latest embulk --version
Embulk v0.9.23

コンテナ内の embulk で csv ファイルを orc 変換

せっかくなので、実際に embulk を使用して csv ファイルを orc ファイルに変換してみる

Dockerファイルを修正

  • 先程の Docker ファイルに以下を追加
    • embulk-output-orc プラグイン のインストール
      • プラグインは、embulk gem install ${パッケージ名}のようにしてインストールする
    • WORKDIR を /work に設定
Dockerfile
FROM java:8

# Install embulk
RUN curl --create-dirs -o /usr/local/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar" &&\
    chmod +x /usr/local/bin/embulk

RUN embulk gem install embulk-output-orc

WORKDIR /work

ENTRYPOINT ["java", "-jar", "/usr/local/bin/embulk"]

build

docker build -t embulk .

作業ディレクトリの構成

.
├── Dockerfile
└── work
    ├── config.yml
    ├── csv_inputs
    │   └── sample_01.csv.gz
    └── orc_outputs

動作イメージ

  • work/config.yml に書いてある設定に沿って、./work/csv_inputs/sample_01.csv.gzを orc 変換して、./work/orc_outputs/ 配下に出力

work/config.yml ファイル中身

  • 先程のクイックスタートでできた config.yml 内の outtype: orc に書き換えている
  • 今回はデータ量が少なく、空ファイルが出来てしまうのを防ぐため、exec の内で、min_output_tasks を1にしてファイルを分散させないようする
config.yml
exec:
  min_output_tasks: 1
in:
  type: file
  path_prefix: /work/csv_inputs/sample_
  decoders:
  - {type: gzip}
  parser:
    charset: UTF-8
    newline: LF
    type: csv
    delimiter: ','
    quote: '"'
    escape: '"'
    null_string: 'NULL'
    trim_if_not_quoted: false
    skip_header_lines: 1
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: id, type: long}
    - {name: account, type: long}
    - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'}
    - {name: purchase, type: timestamp, format: '%Y%m%d'}
    - {name: comment, type: string}
out: 
  type: orc
  path_prefix: /work/orc_outputs/sample
  compression_kind: ZLIB
  overwrite:   true

input となる csv ファイルの中身も、クイックスタートの時と同じ

# Mac では、zcat は gzcat なので注意
gzcat work/csv_inputs/sample_01.csv.gz

id,account,time,purchase,comment
1,32864,2015-01-27 19:23:49,20150127,embulk
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
4,11270,2015-01-29 11:54:36,20150129,NULL

実行

  • コンテナ内の WORKDIR (/work) を、./workでマウントしている
docker run -it --rm -v $(pwd)/work:/work embulk:latest run config.yml

確認

  • orc ファイルができている
ls work/orc_outputs

sample.000.orc

orc ファイルの中身確認

orc-tools data work/orc_outputs/sample.000.orc

log4j:WARN No appenders could be found for logger (org.apache.hadoop.util.Shell).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.apache.hadoop.security.authentication.util.KerberosUtil (file:/usr/local/Cellar/orc-tools/1.6.6/libexec/orc-tools-1.6.6-uber.jar) to method sun.security.krb5.Config.getInstance()
WARNING: Please consider reporting this to the maintainers of org.apache.hadoop.security.authentication.util.KerberosUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Processing data file work/orc_outputs/sample.000.orc [length: 787]
{"id":1,"account":32864,"time":"2015-01-27 19:23:49.0","purchase":"2015-01-27 00:00:00.0","comment":"embulk"}
{"id":2,"account":14824,"time":"2015-01-27 19:01:23.0","purchase":"2015-01-27 00:00:00.0","comment":"embulk jruby"}
{"id":3,"account":27559,"time":"2015-01-28 02:20:02.0","purchase":"2015-01-28 00:00:00.0","comment":"Embulk \"csv\" parser plugin"}
{"id":4,"account":11270,"time":"2015-01-29 11:54:36.0","purchase":"2015-01-29 00:00:00.0","comment":null}
________________________________________________________________________________________________________________________

変換できている!

参考文献

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

kubernetes + docker

! /bin/sh

Modify yum.conf

sed -i -e "/timeout=/d" /etc/yum.conf
sed -i -e "13s/^/timeout=300\n/g" /etc/yum.conf
sed -i -e "/ip_resolve=/d" /etc/yum.conf
sed -i -e "14s/^/ip_resolve=4\n/g" /etc/yum.conf

Add .curlrc

cat <<-EOF > ~/.curlrc
ipv4
EOF

Install conntrack

yum install -y \
conntrack-tools-1.4.4

Install "Docker"

yum install -y \
yum-utils-1.1.31 \
device-mapper-persistent-data-0.8.5 \
lvm2-2.02.185

yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

yum install -y \
docker-ce-19.03.8 \
docker-ce-cli-19.03.8 \
containerd.io-1.2.13

mkdir -p /etc/docker
cat <<-EOF > /etc/docker/daemon.json
{
"dns": ["8.8.8.8"]
}
EOF

systemctl enable docker
systemctl start docker

Install "kubectl"

curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.2/bin/linux/amd64/kubectl
chmod +x ./kubectl
mv -f ./kubectl /usr/local/bin

Install "minikube"

curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.9.2/minikube-linux-amd64
chmod +x minikube
install minikube /usr/local/bin
rm -f minikube

stop firewall

systemctl disable firewalld
systemctl stop firewalld

Add addons

/usr/local/bin/minikube start --vm-driver=none
/usr/local/bin/minikube addons enable ingress

Docker restart and update DNS settings

systemctl restart docker

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

AWSでGPU必須のバッチ処理を動かす

まとめ

  • 使うべきサービスは AWS Batch
  • GPUあり/なしで挙動が変わるライブラリに注意
    • DockerHubにGPUあり版のイメージがある場合はそれをbaseにすると楽
    • 例: PyTorch

背景

  • 以下の要件を満たすシステムをAWS上に組みたい
  • それができそうなサービスがいろいろあるがどれを使えばいいのかよくわからない
    • Fargate
    • ECS
    • SageMaker
    • AWS Batch (採用)

なぜAWS Batch?

  • Fargate
    • GPUが使えないのでNG
    • CPUだけなら、Fargateでもいいと思います
  • ECS
    • GPUインスタンスを立ち上げ続ける必要があるのでNG
  • SageMaker
    • (サービスがありすぎてよくわからない。。。)
  • AWS Batch
    • 特定時間のみ起動して終わったらインスタンスを閉じてくれる
    • スポットインスタンスも使える
    • GPUインスタンスも使える

AWS Batchの使い方

  1. ECR (Elastic Container Registory) に動かしたいコンテナのイメージをpushする
  2. AWS Batchのコンピューティング環境を作る
    1. マネージド型を選択
    2. インスタンスの設定 -> スポット
    3. オンデマンド料金 -> 予算をみて適当に設定。50%だとそんなに待たされることはなかったです。
    4. 許可されたインスタンスタイプ -> GPUが使えるインスタンスを選択。g4dn.xlargeが最安なので、これから様子をみてみるのも良いかもしれません
    5. EC2設定のイメージタイプ -> Amazon Linux 2 (GPU)
  3. AWS Batchのジョブキューを作る
    1. コンピューティング環境は2.で作った物を選ぶ
  4. AWS Batchのジョブ定義を作る
    1. プラットフォーム -> EC2
    2. イメージ -> 1でpushしたもの
    3. コマンド -> バッチで実行したい物をCMDの引数として使えるように書く。元のイメージのCMDは上書きされるので、イメージ側に記載している場合でも改めて設定が必要
    4. メモリ -> 2GBで足りない場合は増やす
    5. GPUの数 -> 1
  5. AWS Batchのジョブを投げる
    1. ジョブ画面から新しいジョブを作成をクリック
    2. ジョブ定義とジョブキューは4と3で作った物を選択
    3. 最後の送信を押すと、ジョブがsubmitされる
  6. AWS Batchのジョブ実行を見守る
    1. ダッシュボード画面からジョブの実行状況が見られるので見守る

コンテナイメージについて

コンテナ側がGPU Readyになっていても、イメージファイル内のソフトウェアがGPU対応してないと、結局CPUで動作してしまう。個人的に注意すべきと思う点は下記。

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

Dockerコンテナ上でAnsibleを実行してみた

はじめに

現場でAnsibleを使う機会ができたので、勉強のためにDockerコンテナ上でAnsibleを動かしてみました。

対象

Ansibleを実際に動かしてみたい人

Ansibleとは?

Ansibleとは、システム構成、ソフトウェアの展開等、様々な設定作業を自動するツールである。
PlaybookというYAML形式のテキストファイルにタスクを記述し、それをAnsibleで実行させることで多様な処理を実現する。
メリットとして、下記2点が挙げられる。

  • 設定ファイルがyamlでの記述になっておりシンプル
  • 冪等性(何度同じ操作しても、同じ状態にする)を担保

試したこと

  • Ansibleコンテナと、環境を適用したいターゲットコンテナを立ち上げる
  • Ansibleコンテナからplaybookを実行して、ターゲットコンテナにgitをインストールする

ファイルの構成

.
├── ansible
│   └── install_git.yml
├── docker
│   ├── ansible
│   │   └── Dockerfile
│   └── target
│       └── Dockerfile
└── docker-compose.yml

ファイルの説明

docker/ansible/Dockerfile

Ansibleとsshの設定

FROM centos:8

RUN yum update -y \
 && yum install -y epel-release \
 && yum install -y ansible \
 && yum -y install openssh-clients \
 && echo "target" >> /etc/ansible/hosts

docker/target/Dockerfile

Ansibleサーバーからsshできるように、sshの設定、sshdを起動

FROM centos:8

RUN yum -y update \
  && yum -y install openssh-server \
  && sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config \
  && sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config \
  && ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa \
  && ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa \
  && sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config \
  && echo "root:" | chpasswd

# sshdを起動
CMD ["/usr/sbin/sshd", "-D"]

docker-compose.yml

Ansibleコンテナと、ターゲットコンテナを起動させる

version: "3"
services:
  ansible:
    tty: true
    working_dir: 
      /opt/ansible
    build:
      context: .
      dockerfile: ./docker/ansible/Dockerfile
    volumes:
      - ./ansible:/opt/ansible
  target:
    tty: true
    build:
      context: .
      dockerfile: ./docker/target/Dockerfile

ansible/install_git.yml

Playbookファイル、gitをインストールさせている

- hosts: target
  tasks:
    - name: install git
      yum: name=git state=latest

実行

  1. コンテナ起動
  2. ansibleサーバーにログインして、playbookを実行
  3. ターゲットコンテナにgitがインストールされているか確認

1. コンテナ起動

# コンテナ起動
~ % docker-compose up -d
Starting ansible_ansible_1 ... done
Starting ansible_target_1  ... done

2. ansibleサーバーにログインして、playbookを実行

# コンテナを表示
~ % docker ps
CONTAINER ID   IMAGE             COMMAND               CREATED             STATUS          PORTS     NAMES
28f39d3e82f0   ansible_ansible   "/bin/bash"           About an hour ago   Up 13 seconds             ansible_ansible_1
362d7471778f   ansible_target    "/usr/sbin/sshd -D"   About an hour ago   Up 13 seconds             ansible_target_1

# Ansbleコンテナにログイン
~ % docker exec -it ansible_ansible_1 bash

# playbook実行
[root@28f39d3e82f0 ansible]# ansible-playbook install_git.yml

PLAY [target] ********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************************
The authenticity of host 'target (172.22.0.2)' can't be established.
ECDSA key fingerprint is SHA256:YJSVW2y2ryrRuU0rn3I8onXAPMwS/k03uj+MNd5JqP0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? y
Please type 'yes', 'no' or the fingerprint: yes
ok: [target]

TASK [install git] ***************************************************************************************************************************************************************************************
changed: [target]

PLAY RECAP ***********************************************************************************************************************************************************************************************
target                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

3. ターゲットコンテナにgitがインストールされているか確認

# ターゲットコンテナにログイン
~ % docker exec -it ansible_target_1 bash

# gitがインストールされているか確認
[root@362d7471778f /]# git --version
git version 2.27.0

参考

https://tech-mmmm.blogspot.com/2018/08/ansible-ansibleplaybook.html

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

【Xdebugのバージョン変更に注意!】Xdebug3 + docker + VSCodeで開発環境を作る

はじめに

docker(windows) + VSCodeの開発環境で、Xdebugを使ってPHPのデバッグ環境も作ろうと思ったのですが
xdebugのバージョンが2から3に上がって、php.iniの記述方法がだいぶ変わっていたのでメモとして共有します。

環境

Docker Desktop (OS:Windows 10 Home)

気づいたらwindows 10 Homeでも、Docker Desktopが使えるようになってました。
まだ試していない方がいたら、インストールすることをオススメします。
このおかげでMacの場合とほぼ同様に開発環境を作れます。

Visual Studio Code

最近流行っているVSCodeです。拡張機能が多くて便利です。

Xdebug 3 (Dockerコンテナ内にインストール)

PHP+Apacheのコンテナを生成して、その中にXdebugをインストールします。
ローカル環境には、上2つ以外にはPHPも何もインストールする必要がないので便利です。

特にバージョンを指定せずにXdebugをインストールすると、現在はバージョン3がインストールされます。
これに伴って、php.iniの書き方が変わっています。(詳しくは以降に説明)

各種インストール

1. Docker Desktopのインストール

Windows Home に Docker Desktop をインストール

下記ページの手順通りにやれば問題ないと思います。(Windows 10 Home)

2. Visual Studio Codeのインストール

VSCode ダウンロードページ

ここからダウンロード、インストールします。

3. VSCodeの拡張機能をインストール

VSCodeの拡張機能として「PHP Debug」をインストールする。
手順:サイドバー「拡張機能」-> 検索欄に「PHP Debug」-> 「PHP Debug」をインストール

インストール後は一応VSCodeを再起動してください。
install.png

DockerコンテナにXdebugをインストール

0. ディレクトリ構成

今回はサンプルということで、簡単に構成しました。
folder.png

1. DockerFile, docker-compose.ymlの記述

今回はより簡単にするように、PHP+ApecheのコンテナにXdebugをコマンドでインストールするようにDockerfileを作成します。

DockerFile
FROM php:7.3-apache

RUN apt-get update && apt-get install -y \
    git \
    unzip \
    vim

# xdebug インストール
RUN pecl install xdebug \
    && docker-php-ext-enable xdebug

docker-compose.ymlもディレクトリ構成に合わせて下記のように記述します。

docker-compose.yml
version: '3'

services:
    app:
        build:
            context: ./docker
        container_name: app
        stdin_open: true
        tty: true
        ports:
            - '5000:80'
        volumes:
            - ./src:/var/www/html
            - ./docker/php.ini:/usr/local/etc/php/php.ini

2. launch.jsonの作成と記述

VSCodeのサイドバー「実行」-> 「launch.jsonファイルを作成します」をクリック-> 環境の選択から「PHP」を選択
この段階でlaunch.jsonが自動生成され、下画像のようなディレクトリ構成になると思います。
launch.png
自動生成されたlaunch.jsonにpathMappingsを追加して、下記のようにしておきます。

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "pathMappings": {
                "/var/www/html/": "${workspaceRoot}/src"
            }
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000
        }
    ]
}

3. php.iniの記述(本題)

今回の記事を書こうと思ったきっかけの部分です。
下記のように記述しました。

php.ini
[xdebug]
; リモートデバッグ有効化
xdebug.mode=debug
; リモートデバッグの自動開始
xdebug.start_with_request=yes
; ホスト指定
xdebug.client_host=host.docker.internal
; ホスト側のポート指定
xdebug.client_port=9000
; VSCODEをIDEとして指定
xdebug.idekey="VSCODE"

Xdebugのバージョンが2から3になるにあたって、書き方の変更が必要でした。

公式ドキュメント:Upgrading from Xdebug 2 to 3

今回記述した設定だと、下記のような変更が必要です。
この他にも書き方が色々と変わっているみたいなので、詳しくは公式ドキュメントを参照してください。

リモートデバッグの有効化

Xdebug 2: xdebug.remote_enable=1
Xdebug 3: xdebug.mode=debug

リモートデバッグの自動開始

Xdebug 2: xdebug.remote_autostart=1
Xdebug 3: xdebug.start_with_request=yes

ホスト指定

Xdebug 2: xdebug.remote_host=host.docker.internal
Xdebug 3: xdebug.client_host=host.docker.internal
※以前は「host.docker.internal」はMac限定だったようですが、現在はWindowsでも使用可能です。

ホスト側のポート指定

Xdebug 2: xdebug.remote_port=9000
Xdebug 3: xdebug.client_port=9000

4. index.phpの作成

サンプルレベルで試してみます。

index.php
<?php
    echo 'Hello World';

5. Dockerコンテナのビルドと起動

Docker Desktopを起動してから、Windows PowerShell または VSCodeの表示-> ターミナルから下記コマンドを実行します。

ターミナル
$ docker-compose up -d --build

デバッグの実行

1. ブレークポイントの設置、デバッグ実行

設置したい行の左をクリックしてブレークポイントを設置します。
breakpoint.png
この状態でF5キーを押すか、緑三角マークの「デバッグの開始」を押すとデバッグが実行されます。

2. ブラウザで接続

docker-composeでポート番号5000を設定したので、ブラウザから接続します。
port.png

3. VSCodeで確認

手順の1~2を実行すると、下記画像のようにブレークポイントで止まります。
これで成功!
stop.png

おわりに

Xdebugのバージョン変更に伴った変化により、自分はデバッグ環境が動かない原因の特定に時間がかかりました。
もしも同じような人がいて、助けになれば幸いです。

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

X-Roadを手軽に試せるSecurity Server Docker Imageが出たので試してみる1

はじめに

X-Roadは、エストニアの政府機関や、政府機関と連携を行う企業(銀行など)の間で使われている情報連携を行うための技術です。

X-Roadはソースコードはgithub.com/nordic-institute/X-Roadにあるものの、Docker Imageやダウンロードしてすぐ動くパッケージがなかったので使ってみた系の記事があまりありませんでした。

最近、Planetway社でX-Roadを手軽に試せるDocker Imageを公開しました[hub.docker.com]
この記事では、X-Roadを知らない方向けに、事前知識を端折りながらまずはX-Roadを使ってみることから始めます。

# full disclosure 私はPlanetway社の中の人です

docker-compose upでX-Road Security Serverを立ち上げる

Docker Imageが公開されているのです。
何はともあれローカルで立ち上げてみましょう。

作業用のディレクトリを作り、Docker Hubのドキュメントにある内容をコピーしてdocker-compose.ymlファイルを作成します。

% cat docker-compose.yml
version: '3.7'

services:
  ss01:
    image: planetway/xroad-securityserver:6.24.1-1
    command: bash -c "/files/initdb.sh && /files/cmd.sh"
    depends_on:
      - postgres
    environment:
      # JP-TEST or JP
      - PX_INSTANCE=JP-TEST
      - PX_MEMBER_CLASS=COM
      - PX_MEMBER_CODE=0170121212121
      - PX_SS_CODE=qiita-demo-01
      - PX_TSA_NAME=TEST of Planetway Timestamping Authority 2020
      - PX_TSA_URL=https://tsa.test.planetcross.net
      - PX_TOKEN_PIN=jXq+rlg2VS
      - PX_ADMINUI_USER=admin
      - PX_ADMINUI_PASSWORD=Secret222
      - POSTGRES_HOST=postgres
      - POSTGRES_PORT=5432
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - PX_SERVERCONF_PASSWORD=serverconf
      - PX_MESSAGELOG_PASSWORD=messagelog
      - PX_OPMONITOR_PASSWORD=opmonitor
      - PX_POPULATE_DATABASE=true
      - PX_ENROLL=true
    ports:
      - "2080:2080"
      - "4000:4000"
      - "5500:5500"
      - "5577:5577"
      - "5588:5588"
      - "8000:80"
      - "8443:443"
    volumes:
      # .p12 files and keyconf.xml
      - "px-ss-signer:/etc/xroad/signer"
      # mlog.zip files are stored here, and ./backup contains backups
      - "px-ss-xroad:/var/lib/xroad"

  postgres:
    image: postgres:10
    environment:
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - "px-ss-postgres:/var/lib/postgresql/data"

volumes:
  px-ss-postgres:
  px-ss-signer:
  px-ss-xroad:

Docker Hubのドキュメントでは環境変数を...と省略しているところはとりあえず適当に埋めます。

PX_SS_CODEはX-Roadメンバー(X-Roadに参加する組織)の中のSecurity Serverを表すユニークな文字列です。ここからコピペする際には、ユニークな文字列に変更してください。

そしてdocker-compose upしてSecurity ServerとPostgreSQLをまとめて起動します。

% docker-compose up
Creating network "px-ss-docker_default" with the default driver
Creating px-ss-docker_postgres_1 ... done
Creating px-ss-docker_ss01_1     ... done
Attaching to px-ss-docker_postgres_1, px-ss-docker_ss01_1
postgres_1  | The files belonging to this database system will be owned by user "postgres".
postgres_1  | This user must also own the server process.
postgres_1  |
postgres_1  | The database cluster will be initialized with locale "en_US.utf8".
postgres_1  | The default database encoding has accordingly been set to "UTF8".
postgres_1  | The default text search configuration will be set to "english".
postgres_1  |
postgres_1  | Data page checksums are disabled.
postgres_1  |
postgres_1  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
postgres_1  | creating subdirectories ... ok
postgres_1  | selecting default max_connections ... 100
postgres_1  | selecting default shared_buffers ... 128MB
postgres_1  | selecting default timezone ... Etc/UTC
postgres_1  | selecting dynamic shared memory implementation ... posix
postgres_1  | creating configuration files ... ok
ss01_1      | 2021-01-11 09:00:06,566 Creating /var/lib/xroad/backup
ss01_1      | 2021-01-11 09:00:06,655 Generating new internal.[crt|key|p12] files
ss01_1      | /CN=0874c65254b7 -subj
ss01_1      | Generating a RSA private key
ss01_1      | ............................................................+++++
ss01_1      | ............+++++
ss01_1      | writing new private key to '/etc/xroad/ssl/internal.key'
ss01_1      | -----
ss01_1      | 2021-01-11 09:00:07,004 Generating new proxy-ui-api.[crt|key|p12] files
...
# しばらく待つと
ss01_1      | {"timestamp":"2021-01-11T09:03:10.183Z","level":"INFO","thread":"main","logger":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer","message":"Tomcat started on port(s): 4000 (https) with context path ''","context":"X-Road Proxy Admin REST API"}

↑の最後の行が出てきたら、
https://localhost:4000/ をブラウザで開いてみしょう。
X-Road Security Serverの管理画面です。

Safariでは"接続はプライベートではありません", Chromeでは"この接続ではプライバシーが保護されません"と表示されますが、理解しているので先に進みます。

ログイン画面では環境変数のPX_ADMINUI_USERPX_ADMINUI_PASSWORDでログインします。

スクリーンショット 2021-01-11 11.54.39.png

X-Roadとは

今一体何を立ち上げたのでしょうか。
Security Serverとは何でしょうか。

arc-g_deployment_view_of_x_road.png
図1 Deployment view of X-Road (赤い線は筆者による強調)

docker-compose.ymlに書いたplanetway/xroad-securityserver Docker Imageは、上図のClient Security Server, Service Security Serverの青い箱に当たるソフトウェアです。

X-Roadの情報連携とは、Service Clientという組織内のClient Information Systemから、Service Providerという組織内のService Information Systemが提供する機能を使うことを表します。

Security Serverは、Client Information Systemから見るとForward Proxyの役割を果たし、Service Information Systemから見るとReverse Proxyの役割を果たします。Security ServerはClient Information SystemとService Information Systemをインターネットを通してつなげるセキュアなトンネルを構築します。

Client Information SystemとClient Security Serverの間、そしてService Information SystemとService Security Serverの間は、SOAPとRESTプロトコルを話します。

X-Roadの情報連携をやってみましょう。

認証用証明書のActivate、Security Serverの登録とSubsystemの登録

planetway/xroad-securityserver Docker Imageは、Security Server立ち上げ時のセットアップ手順をいくつか自動的に実行しますが、情報連携を行う=HTTPリクエストをSecurity Serverに送る、までにはまだいくつかステップがあります。

認証用証明書をActivateしましょう。

https://localhost:4000/ を開き、ログインします。
"KEYS AND CERTIFICATES" をクリック -> "Token: softToken-0"の左の ">" をクリック -> "TEST of Planetway Intermediate L1 Organizations CA 2020 ..." をクリック -> "ACTIVATE" をクリックします。

スクリーンショット 2021-01-12 16.55.30.png

スクリーンショット 2021-01-12 16.52.40.png

認証用証明書をActivateしました。
Security Serverの登録リクエストを送りましょう。

バツを押してその画面を閉じ、"KEYS AND CERTIFICATES" をクリック -> "Register" ボタンを押します。

スクリーンショット 2021-01-12 16.34.20.png

"127.0.0.1"と入力し "ADD" ボタンを押します。
これでSecurity Serverの登録リクエストを送信したことになります。

図1のCentral Serverでは、X-Roadに参加する組織と、それぞれの組織が運用するSecurity Serverのカタログを管理しています。Security Serverの登録リクエストは、Central ServerのカタログにこのSecurity Serverを登録してもらうためのリクエストです。

次にSubsystemの登録を行います。
"CLIENTS" をクリック -> "Add Subsystem" ボタンを押します。

"Subsystem Code"には"democlient"と入力し、"ADD SUBSYSTEM"ボタンを押します。

スクリーンショット 2021-01-15 12.01.51.png

"CLIENTS"を押し以下のように表示されていれば(Docker Demo Company (Owner)とdemoclientの行に緑の円と"REGISTERED"が表示されていれば)Subsystemの登録は完了です。

スクリーンショット 2021-01-12 19.25.42.png

図1におけるService Client組織に所属するClient Information System情報システムをdemoclientと言う名前で登録しました。

Security ServerのConnection TypeをHTTPに変更する

もうそろそろです。

Client Information SystemとClient Security Serverの間は、デフォルトではHTTPSを使用します。この記事では簡略化するためHTTPに変更します。

同管理画面で、"CLIENTS" をクリック -> "democlient" の文字のところをクリック -> "INTERNAL SERVERS" をクリックします。下図のように、"CONNECTION TYPE"を"HTTP"に変更してください。

スクリーンショット 2021-01-13 14.26.27.png

HTTPリクエストをSecurity Serverに送る

Service Provider側は、この記事では、予めPlanetway社が提供するものを利用しましょう。Planetway社では、現在時刻を返すtimeというX-Roadサービスをデモとして提供しています。

以下のようにcurlのコマンドを実行してください。

% curl -v "http://localhost:8000/r1/JP-TEST/COM/0170368015672/demoprovider/time" -H "X-Road-Client: JP-TEST/COM/0170121212121/democlient"
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /r1/JP-TEST/COM/0170368015672/demoprovider/time HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
> X-Road-Client: JP-TEST/COM/0170121212121/democlient
>
< HTTP/1.1 200 OK
< Date: Wed, 13 Jan 2021 12:33:54 GMT
< Content-Type: text/plain;charset=utf-8
< x-road-id: JP-TEST-b3fcf843-4d50-4108-b25a-22b5993ad0c9
< x-road-client: JP-TEST/COM/0170121212121/democlient
< x-road-service: JP-TEST/COM/0170368015672/demoprovider/time
< x-road-request-id: f13d39d1-c5a7-4fc5-bd2f-ac002e2dc2e7
< x-road-request-hash: N02MYtDKDzpT1QN7uUQlgUa+I5Vu5ImpX1WKxj/fByjKCUITbWgAlySJwfSt0xQU1AzaPG0RiEqoONQBxXD0jQ==
< Content-Length: 41
<
{"now":"2021-01-13T12:33:54.692995982Z"}

と表示されれば成功です!

このcurlコマンドを紐解いていきます。

localhost:8000 私の環境では、DockerはmacOS用のDocker Desktopで動作させています。この記事冒頭のdocker-compose.ymlではports以下に- 8000:80とありました。これはlocalhost:8000をDocker containerの80番ポートに転送します。Docker container内のSecurity Serverはポート80番でlistenしています。

/r1/JP-TEST/COM/0170368015672/demoprovider/time Information SystemとSecurity Serverの間のプロトコルは、X-Road: Message Protocol for RESTと言うプロトコルにしたがいます。URLのpath部分はMessage Protocol for REST文書の中でserviceIdとして記載があります。
r1はプロトコルのバージョン。
JP-TESTはX-RoadインスタンスのIDです。X-Roadのインスタンスとは、図1に表される箱全部を含んだまとまりで、あるGoverning Authority/運営機関と、運営機関が提供する環境で情報連携を行う組織群を含みます。JP-TESTはPlanetway社が運営機関を務めています。他にEE(エストニア), FI(フィンランド)などがあります。
COMはX-Roadに参加する組織が法人であることを表します。他にGOVなどがあります。
0170368015672はPlanetway社を表すX-Road Member IDです。0170121212121はDocker Imageの使用者が使えるテスト用のX-Road Member IDです。
demoproviderはPlanetway社の情報システムを表すSubsystem IDです。
timeはX-Road Serviceコードです。

X-Road-Client: JP-TEST/COM/0170121212121/democlient というヘッダの値は、Client Information Systemを表しています。

Security Serverはアクセスコントロールの機能も含みます。予めtimeサービスは、JP-TEST/COM/0170121212121/democlientから呼び出せるよう設定済みです。

time X-Road Serviceの実体

timeサービスは以下のようなGolangのコードとして実装しています。

package main

import (
    "encoding/json"
    "net/http"
    "os"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    t := time.Now()

    enc := json.NewEncoder(w)
    err := enc.Encode(map[string]interface{}{
        "now": t,
    })
    if err != nil {
        http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        return
    }
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    http.HandleFunc("/", handler)
    http.ListenAndServe(":"+port, nil)
}

普通のJSONを返すAPIサーバですね。http.HandleFuncの第一引数が/であることに注意。これが図1のService Information Systemに当たります。

いくつか私が面白いと思う点を書きます。

  1. Security ServerがInformation Systemから見るとシンプルなトンネルであること

    このGolangのサーバをローカルで実行し、立ち上がったサーバにcurlでHTTPリクエストを送る手順と、インターネットのどこかでPlanetway社が提供するtimeサービスを呼び出す手順の違いが小さいこと。X-Road-Clientリクエストヘッダ、URLのpath部分だけです。

  2. X-Roadのセキュリティ面はInformation Systemには見えない

    よくある外部APIと比較するとどうでしょう。Bearer tokenのようなものもクライアント証明書も何も見えません。認証や認可はSecurity Serverが隠蔽しています。

  3. curlのリクエスト送り先はSecurity Server

    知る必要があるのは、組織のID、その中のサービスIDからなるserviceIdだけです。相手のURLは知る必要がありません。インターネットのレイヤの上に綺麗にRPCのレイヤをのっけています。

まとめ

DockerでX-Road Security Serverを立ち上げ、それを通して時刻を返すデモサービスを呼び出すcurlコマンドを実行しました。

続く

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

(自分用)docker+code-server、cloud9でC#環境作ってみる

はじめに

この記事の続きみたいな感じです。
dockerでcode-serverを構築して、そこでC#環境作れるか試してみます。

code-server_1

以下のようなディレクトリを作ります。

ls
> Dockerfile  code-server  docker-compose.yml  projects

Dockerfileとdocker-compose.ymlはこんな感じです。
公式のdocker-imageを使ってそこに.net coreをinstallしています。

Dockerfile
Dockerfile
FROM codercom/code-server:3.8.0

USER root

# Install wget and other module
RUN apt-get update && apt-get install -y \
    wget

# Install code-server plugin
# ※commandからのextension installは効かない?
RUN code-server --install-extension ms-dotnettools.csharp

# Install .NET5
ENV DOTNET_ROOT=/usr/share/dotnet
ENV PATH=/usr/share/dotnet:/root/.dotnet/tools:$PATH
# 以下の環境変数を変える必要あり
# 詳細1: https://github.com/dotnet/core/issues/2186
# 詳細2: https://github.com/dotnet/core/blob/master/Documentation/build-and-install-rhel6-prerequisites.md
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true

RUN wget -O dotnet.tar.gz https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz \
    && wget -O dotnet_runtime.tar.gz https://download.visualstudio.microsoft.com/download/pr/6bea1cea-89e8-4bf7-9fc1-f77380443db1/0fb741b7d587cce798ebee80732196ef/aspnetcore-runtime-5.0.1-linux-x64.tar.gz \
    && dotnet_sha512='398d88099d765b8f5b920a3a2607c2d2d8a946786c1a3e51e73af1e663f0ee770b2b624a630b1bec1ceed43628ea8bc97963ba6c870d42bec064bde1cd1c9edb' \
    && echo "$dotnet_sha512  dotnet.tar.gz" | sha512sum -c - \
    && dotnet_runtime_sha512='fec655aed2e73288e84d940fd356b596e266a3e74c37d9006674c4f923fb7cde5eafe30b7dcb43251528166c02724df5856e7174f1a46fc33036b0f8db92688a' \
    && echo "$dotnet_runtime_sha512  dotnet_runtime.tar.gz" | sha512sum -c - \
    && mkdir -p "/usr/share/dotnet" \
    && mkdir -p "/usr/bin/dotnet" \
    && mkdir -p "/root/.dotnet/tools" \
    && tar zxf dotnet.tar.gz -C "/usr/share/dotnet" \
    && rm dotnet.tar.gz \
    && tar zxf dotnet_runtime.tar.gz -C "/usr/share/dotnet" \
    && rm dotnet_runtime.tar.gz \
    && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
    && dotnet help

USER coder

docker-compose.yml
docker-compose.yml
version: "3"
services:
  code:
    build:
      context: .
      dockerfile: Dockerfile
    restart: always
    environment:
      PUID: 1000
      PGID: 1000
      PASSWORD: password
    ports:
      - 20000:8080
    volumes:
      - ./projects:/home/coder/project
      - ./code-server:/home/coder/.local/share/code-server

起動します。

docker-compose up -d --build

URLにアクセスします。
image.png

DockerfileのこのコマンドでC#の拡張機能を入れたつもりなのですが
どうも入っていないみたい?です。
ターミナルから同じようにコマンドを叩いてもUIの方には反映されませんでした。

RUN code-server --install-extensions ms-dotnettools.csharp

仕方ないので拡張機能は手動で入れます。
主だった3つを入れてみました。
image.png

適当なプロジェクトを作ってみます。

dotnet new console -o tutorial
ls
> Program.cs  tutorial.csproj

tutorialフォルダ以下にファイルを追加します。
C# extensionsが使えそうな雰囲気があるのですがファイルの生成はしてくれませんでした。
image.png

仕方ないので通常のファイル作成で名前をCalc.csとします。
Program.csとCalc.csを以下の内容にします。

Calc.cs
Calc.cs
using System;

namespace tutorial2
{
    class Calc
    {
        public static double Add(double a, double b)
        {
            return a + b;
        }
    }
}

Program.cs
Program.cs
using System;
using tutorial2;

namespace tutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var ret = Calc.Add(1, 3);
            Console.WriteLine($"Cal Result is {ret}");
        }
    }
}

ターミナルから実行してみます。
image.png
実行できました。

デバッグはどうでしょうか?
launch.jsonに.Net Core Launchを指定し、ブレークポイントを置いて実行してみます。

???以下のようなエラーがDEBUG CONSOLEに表示されます。
image.png

このURLを見てみると、デバッガーはMicrosoftのIDEでないと動作しないライセンスになっているみたいです。
code-serverでのissueにも挙がっていて独自デバッガー?でやる方法しかないみたいです。。。
issueもライセンスの問題により標準デバッガーは使えないという事で閉じられていました。

code-server_2

linuxserver/code-serverにDotNetCoreを入れて有志の方が作った
imageがあったのでそっちも乗っけておきます。

git clone https://github.com/ptr727/VSCode-Server-DotNetCore.git
cd VSCode-Server-DotnetCore
docker-compose up -d # 起動

cloud9

おまけでcloud9でも試してみます。

Dockerfileとdocker-compose.ymlはこんな感じです。
linuxserver/cloud9にDotNetCoreを入れる形にしてみました。

Dockerfile
Dockerfile
FROM linuxserver/cloud9:version-1.27.4

RUN mkdir -p /code/projects

# Install wget
RUN apt-get update \
    # Install wget
    && apt-get install -y wget

# Install .NET5
ENV DOTNET_ROOT=/usr/share/dotnet
ENV PATH=/usr/share/dotnet:/root/.dotnet/tools:$PATH
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true

RUN wget -O dotnet.tar.gz https://download.visualstudio.microsoft.com/download/pr/a0487784-534a-4912-a4dd-017382083865/be16057043a8f7b6f08c902dc48dd677/dotnet-sdk-5.0.101-linux-x64.tar.gz \
    && wget -O dotnet_runtime.tar.gz https://download.visualstudio.microsoft.com/download/pr/6bea1cea-89e8-4bf7-9fc1-f77380443db1/0fb741b7d587cce798ebee80732196ef/aspnetcore-runtime-5.0.1-linux-x64.tar.gz \
    && dotnet_sha512='398d88099d765b8f5b920a3a2607c2d2d8a946786c1a3e51e73af1e663f0ee770b2b624a630b1bec1ceed43628ea8bc97963ba6c870d42bec064bde1cd1c9edb' \
    && echo "$dotnet_sha512  dotnet.tar.gz" | sha512sum -c - \
    && dotnet_runtime_sha512='fec655aed2e73288e84d940fd356b596e266a3e74c37d9006674c4f923fb7cde5eafe30b7dcb43251528166c02724df5856e7174f1a46fc33036b0f8db92688a' \
    && echo "$dotnet_runtime_sha512  dotnet_runtime.tar.gz" | sha512sum -c - \
    && mkdir -p "/usr/share/dotnet" \
    && mkdir -p "/usr/bin/dotnet" \
    && mkdir -p "/root/.dotnet/tools" \
    && tar zxf dotnet.tar.gz -C "/usr/share/dotnet" \
    && rm dotnet.tar.gz \
    && tar zxf dotnet_runtime.tar.gz -C "/usr/share/dotnet" \
    && rm dotnet_runtime.tar.gz \
    && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
    && dotnet help

docker-compose.yml
docker-compose.yml
version: "3"
services:
  code:
    build:
      context: .
      dockerfile: Dockerfile
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Tokyo
      - GITURL=https://github.com/linuxserver/docker-cloud9.git #optional
      - USERNAME=user #optional
      - PASSWORD=password #optional
    ports:
      - 50000:8000
    volumes:
      - ./projects:/code/projects
      - ./docker.sock:/var/run/docker.sock #optional

URLにアクセスします。
image.png

code-server同様に適当なプロジェクトをターミナルから作って試します。
割愛しますが、code-server同様dotnet runまでちゃんとできました。
cloud9はインテリセンスやヒストリーがちゃんと効くのでいいですね。

デバッグはどうでしょうか?
ブレークポイントは置けますがRunnerをどう作るのかわからず断念しました。。。
image.png
AWS Cloud9の方だとできるみたいですがこっちだとどうなのでしょうね?

結論

web-ide自体は有用で便利だと思いますが
構築後にも周辺知識が必要だったり、拡張機能がうまく効かない等で本格的な開発はまだ難しそうという印象を受けました。
もっとレベルの高い人だと出来るのでしょうが私ではちょっと。。。
現状は大人しくwindows+visual studioでC#の開発を行うのがやはり無難でしょうか。
ただ、docker+code-server or cloud9でpythonとかの記事はちらほらあるのでそっちだと問題なく使えるのでしょうか。
気が向いたらpython+αで試してみたいと思いました。

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

緊急事態宣言下だし、人狼ゲームを実装してみた【Docker・JavaScript】

動機

春先の緊急事態宣言下ではZoom飲みが大流行したせいか、オンラインの人狼が重くなって困りました。また発令されたし、みんなが自分で人狼ゲームを起動できたら幸せなのでは?というノリで実装&公開したので紹介します。

紹介

以下のような簡単なUIのチャット人狼です。ホストの一人がDockerでゲームを立ち上げたら、ほかの人はブラウザ(スマホでもOK)からアクセスするだけで簡単に遊べます。

image.png

遊び方(ホスト)

Dockerコンテナの起動

まず以下のコマンドでDockerコンテナを起動します。初回はGitHub Container Registryからイメージがpullされます。

docker container run -it --rm -p 3000:3000 ghcr.io/dr666m1/werewolf

起動後、画面の指示に従ってゲームの設定を行ってください。
image.png

ポートの公開

3000番ポートでゲームが起動しているので、パブリックIPアドレスのある環境(GCEなど)ならみんなにIPアドレスを伝えるだけです。注意点としては、http://...:3000のようにポート番号を明示する必要があるかもしれないことと、ファイアウォールの設定を確認しておくことです。

パブリックIPアドレスのない環境ではngrokを利用するのが便利です。初期設定が完了するとngrok http 3000というコマンドで3000番ポートを公開できるようになります。コマンド後にURLが表示されるので、それをみんなに伝えてください。無料枠での制限はこちらご確認ください。

遊び方(全員)

ホストから教えてもらったURLにブラウザでアクセスしましょう。以下のような画面が表示されたら名前を決めて入場してください。

image.png

その後は普通の人狼ゲームです。役職は現状「市民」「人狼」「占い師」「霊媒師」「狩人」のみ1と標準的なので、迷うことはないと思います。

実装

Node.jsで実装しています。GitHubに全てのコードが置いてあるので興味があればご覧ください。似たようなものを作る人の参考に、利用したパッケージを簡単に紹介します。全部npm installで使えるはずです。

Next.js

Next.jsReactでUIを作成するためのフレームワークです。SSG(static generation)やSSR(server-side rendering)を利用できるのが特徴です。今回はnext exportで静的なHTMLファイルを出力するところまでNext.jsで実装しました2。GitHubリポジトリのclientディレクトリ以下が関連ファイルです。

bulma

bulmaはCSSフレームワークです。DOMのクラスを設定するだけで見た目をいい感じに整えてくれます(例えば以下はnotificationクラス)。bulmaのおかげで、今回CSSはほとんど書いていません3
image.png

Next.jsの枠組みで利用するには、_app.jsの冒頭に一行追記するだけです。

_app.js
import "bulma/css/bulma.css" // 追記

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

socket.io

socket.ioはウェブソケットを扱うパッケージで、チャットの実装に利用しました。wsというパッケージもあるのですが、socket.ioの方が多機能なので今回はそちらを使っています(例:ウェブソケットが使えない状況でロングポーリングに切り替えられる、roomnamespaceという概念でクライアントを整理できる)。ちょっと今回の実装では活かしきれなかった部分も多いのですが。

後書き

JavaScriptは全くの初心者なので、バグがあったらすみません。Zoom飲みとかにご活用ください。noteにも稀に投稿しているのでよければご覧ください(直近の記事)。


  1. 要望があれば「狂人」とかいろいろ追加するかもしれません。 

  2. custom serverを利用すればNext.jsの枠組み内で実装を完了できそうでしたが、UIに集中したくて分離しました。Dockerfileを見ての通り、多段階ビルドになっています 

  3. JavaScriptの中で多少スタイルをいじった程度です。 

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

【ギリ初心者向け】Laravel Docker AWS(EC2) Webアプリ(PHP)を0から簡単にデプロイする方法(無料)② ーDocker開発環境構築編(Laravel)ー

0.概要 

何度もいいますが、知らない単語が出た瞬間ググってください!!!!!
①の全体像編がこちらにあるのでこちらを一読してからだと理解がスムーズかと!!!
https://qiita.com/SG_Sg/items/6b8ce48567b6b6602805 
今回で作成するは具体的にいうと赤いとこ
qiita-square

Dokcerを利用してLaravel(PHP)のプロジェクトを作成(dockerの説明)

さて具体的にDockerをを利用して環境構築したいが、、、、
「Dockerってなんやねん」「Docker使うメリットなんやねん」「開発現場でどうやって使うねん」
疑問だらけのため、ざっっくり説明してからにしよう!(最初いくらググってもマジでちょっと何言ってるかわかんないっすねって感じでした)
Dockerとは??等細かいところはググってくださいな、、

1.Dockerのメリット、なぜプロのエンジニアは使うのか

①MACを汚さずに環境とプロジェクト、プログラムを作れる!
どういうことかって???
Dockerを使わずにプロジェクトをつくるとどうなるかみてみよう!!
qiita-square
こんな風にバージョン違いだったり他のPCの環境に依存してしまう!!

Dockerを使うプロジェクトをつくるとどうなるかみてみよう!!
qiita-square

Dockerを使えばDockerの中に環境をインストールしてプロジェクトごとgithub上にのっけるイメージ
Docker内に必要な環境をインストールするから
Macに必要な環境をインストールしなくて良い!!

2.まずはいろいろ準備(GitとかDockerをインストールとか)

以下デフォルトのターミナル使ってるよ!
①githubアカウントの作成
https://github.com
②Gitの初期設定
すみません。。「Mac git初期設定」でググってください汗
最終的に下記のように表示されればokです。

MacBook-Pro% git config --list | grep user
user.name=[githubname]
user.email=67626524+[githubname]@users.noreply.github.com

③GithubSSH接続設定
これも「Mac Github SSH接続」とかでググってね!エラーとかがでたらまたそのエラー文をぐぐるんや!!!

MacBook-Pro% ssh -T github.com
Hi [githubの名前]! You've successfully authenticated, but GitHub does not provide shell access.

こんなかんじにsuccessfullyの文字が出れば大体いける!

④ docker, docker-compose(起動したり停止できたりするやつ)のインストール
Docker for Macをインストール
https://docs.docker.com/docker-for-mac/install
インストール確認(なにかインストールしたら必ずインストールされてるか確認!)

MacBook-Pro% docker --version
Docker version 20.10.0, build 7287ab3
MacBook-Pro% docker-compose --version
docker-compose version 1.27.4, build 40524192

Docker起動確認
スクリーンショット 2021-01-10 15.42.16.png
これで準備完了なはず!!

3.Dockerプロジェクトを作る!!

3-1.github動作確認まで

①プロジェクト(ディレクトリ)を作成する
私はホームディレクトリに「LaravelProjext」をつくってその中に「docker-test」ディレクトリを作成!!このへんはご自由に!!!

MacBook-Pro % mkdir LaravelProject
MacBook-Pro % cd LaravelProject
MacBook-Pro LaravelProject % mkdir docker-test
MacBook-Pro LaravelProject % cd docker-test 
MacBook-Pro docker-test % 

②リモートリポジトリを作る
作り方は「giuhubリモートリポジトリ 作成」でググろう!!
「DockerLaravelTestProject」を作成
スクリーンショット 2021-01-10 16.04.20.png

②リモートリポジトリにテストプッシュ!Gitが機能しているかの確認

//README.mdファイルを作成
MacBook-Pro docker-test % echo "README import First" >> README.md
//git管理できるようにする
MacBook-Pro docker-test % git init
//ステージングにaddする!
MacBook-Pro docker-test % git add . 
//commitする!!
MacBook-Pro docker-test % git commit -m "first commit README"
//push先のリモートリポジトリをGithubに!!(SSHで接続 ※URLはSSH)
MacBook-Pro docker-test % git remote add origin git@github.com:SugiKoki/DockerLaravelTestProject.git
//リモートリポジトリ先を確認!しっかりgithubのプロジェクトになっている
MacBook-Pro docker-test % git remote -v
origin  git@github.com:SugiKoki/DockerLaravelTestProject.git (fetch)
origin  git@github.com:SugiKoki/DockerLaravelTestProject.git (push)
//gitのブランチを確認
MacBook-Pro docker-test % git branch
* master
//gitのブランチ(master)にプッシュ
MacBook-Pro docker-test % git push -u origin master
kokisugi@sugihirokinoMacBook-Pro docker-test % 

③VScodeで確認する
スクリーンショット 2021-01-10 16.58.18.png
しっかりディレクトリの中に書かれていることを確認!!これで作業できる!!!!

3-2.最終ゴールの確認

ファイル構成はこんな感じをめざすよ!!
スクリーンショット 2021-01-10 17.06.23.png
backendの中が実際にプログラミングするところ!!

3-3.アプリケーションサーバー(app)を作る(入れる)

①docker-compose.yml を作成する

MacBook-Pro docker-test % touch docker-compose.yml

VScodeでディレクトリを開いて以下のようにする
qiita-square
以下が内容

version: "3.8"
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work
  web:
    image: nginx:1.18-alpine
    ports:
      - 10080:80
    volumes:
      - ./backend:/work
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /work
  db:
    build: ./infra/mysql
    volumes:
      - db-store:/var/lib/mysql
volumes:
  db-store:

上のappってとこがアプリケーションサーバー
真ん中のwebってとこがwebサーバー
下のdbがデータベースサーバー

作りたい環境をここで設定してこれに合わせてDockerにインストールしていく!!!

②./docker/php/Dockerfile を作成する

MacBook-Pro docker-test % mkdir -p infra/php
MacBook-Pro docker-test % touch infra/php/Dockerfile

下記のコードを Dockerfile へ。
スクリーンショット 2021-01-10 17.28.12.png

FROM php:7.4-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer

RUN apt-get update && \
  apt-get -y install git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /work

ここで行っていること
Composerコマンドのインストール
Laravelで必要な
bcmath, pdo_mysql が不足しているのでインストール

③./docker/php/Dockerfile を作成する

MacBook-Pro docker-test % touch infra/php/php.ini

スクリーンショット 2021-01-10 17.35.52.png

以下のコードをphp.iniへ

zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

3-4.ウェブサーバー(web)を作る(入れる)

①docker/nginx/default.conf を作成する

MacBook-Pro docker-test % mkdir infra/nginx
MacBook-Pro docker-test % touch infra/nginx/default.conf

スクリーンショット 2021-01-10 17.44.41.png
以下のコードを貼り付ける!!

server {
    listen 80;
    server_name example.com;
    root /work/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

3-5.データベース(db)サーバーを作る(入れる)

①./docker/mysql/Dockerfile を作成する

MacBook-Pro docker-test % mkdir infra/mysql
MacBook-Pro docker-test % touch infra/mysql/Dockerfile

スクリーンショット 2021-01-10 17.58.18.png

以下のコードを貼り付ける!

FROM mysql:8.0

ENV MYSQL_DATABASE=sg_db \
  MYSQL_USER=sg \
  MYSQL_PASSWORD=sg \
  MYSQL_ROOT_PASSWORD=sg \
  TZ=Asia/Tokyo

COPY ./my.cnf /etc/mysql/conf.d/my.cnf
RUN chmod 644 /etc/mysql/conf.d/my.cnf

ここは任意なので好きにしてもらって結構!
データベースを接続するときの名前とパスワードだから
忘れないようにおぼえておこう!

  MYSQL_USER=sg \
  MYSQL_PASSWORD=sg \
  MYSQL_ROOT_PASSWORD=sg \

②docker/mysql/my.cnf を作成する

MacBook-Pro docker-test % touch infra/mysql/my.cnf

スクリーンショット 2021-01-10 17.59.49.png

以下のコードを貼り付ける

[mysqld]
# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_0900_ai_ci

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# Error Log
log-error = mysql-error.log

# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

# General Log
general_log = 1
general_log_file = mysql-general.log

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

これで3つのコンテナが完成!!!!しっかり最終的なディレクトリ構成になりましたか???

3-6.Docker起動!(中身の確認)

①dockerを動かしたいときに使うよ!

MacBook-Pro docker-test % docker-compose up -d --build
・・・・
Successfully built 9813d5181de8
Successfully tagged docker-test_db:latest
Creating docker-test_app_1 ... done
Creating docker-test_db_1  ... done
Creating docker-test_web_1 ... done
MacBook-Pro docker-test % 

上の感じにdoneとでたらそれらはOKということ!
・動かした状態でどう動いてるのか確認!

MacBook-Pro docker-test % docker-compose ps
      Name                     Command               State           Ports        
----------------------------------------------------------------------------------
docker-test_app_1   docker-php-entrypoint php-fpm    Up      9000/tcp             
docker-test_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp  
docker-test_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:10080->80/tcp

ちなみにリスタートしたりしたかったら、一度止めたかったらdown!

MacBook-Pro docker-test % docker-compose down
//一度止めたらまた起動しよう
MacBook-Pro docker-test % docker-compose up -d --build

Dockerに入れたそれぞれサーバー(コンテナ)内に入れたのバージョン確認する方法

・appサーバー

MacBook-Pro docker-test %  docker-compose exec app bash
//PHPのバージョン確認
root@5ce9c9fa1435:/work# php -V
//composerのバージョン確認
root@5ce9c9fa1435:/work# composer -v
//インストール済みの拡張機能一覧の確認
root@5ce9c9fa1435:/work# php -m
//dockerから出る
root@5ce9c9fa1435:/work# exit 

・webサーバー

MacBook-Pro docker-test % docker-compose exec web nginx -v

・DBサーバー

MacBook-Pro docker-test % docker-compose exec db bash
//mysqlのバージョン確認
root@6bcf6de7e31e:/# mysql -V
//dockerから出る
root@6bcf6de7e31e:/work# exit 

これでDocker環境構築終了だ!!!さあLaravelを入れてプログラミングしていきます!!!

定期的にGitコミットはしていったほうがよいですよ!

MacBook-Pro docker-test % git add .
MacBook-Pro docker-test % git commit -m  "laravel commit"
MacBook-Pro docker-test % git push

3-7.Laravelをインストールする(appサーバー上でやります)

①appに入って、Laravelをインストール

MacBook-Pro docker-test % docker-compose exec app bash
root@5ce9c9fa1435:/work# composer create-project --prefer-dist "laravel/laravel=8.*" .
//laravelバージョン確認
root@5ce9c9fa1435:/work# php artisan -V
Laravel Framework 8.21.0
root@5ce9c9fa1435:/work# exit 

②Laravel ウェルカム画面の表示
http://127.0.0.1:10080
にアクセス!!!
スクリーンショット 2021-01-10 18.41.21.png
・VScode確認するとbackendこんな感じになっているはず!
スクリーンショット 2021-01-10 18.44.21.png

3-8.プログラミングしてみる!!(html,blade.phpファイルを作って表示できるか確認!)

HTML(.blade.php)を表示してみよう!!MVCモデルで進めていきます!

①backend/resources/viewsのなかに
helloディレクトリを作成
helloディレクトリの中にhello.blade.phpを作成

スクリーンショット 2021-01-10 18.53.45.png
中身はこれとする。貼り付けてOKです!

<html>
    <body>
        <h1>HELLO Larabel<h1>
    </body>
</html>

②URLから呼び出すコントローラーを設定!
・backend/routes/web.phpを修正
スクリーンショット 2021-01-10 21.01.54.png
以下のコードをbackend/routes/web.phpに追加する!

/// URL/helloのとき「HelloController」を呼び出す
Route::get('hello', 'App\Http\Controllers\HelloController@index');

③backend/app/http/Controllers/の中に
HelloController.phpを作成する

スクリーンショット 2021-01-10 21.06.24.png
以下のコードをbackend/app/http/Controllers/の中に
HelloController.phpに追加する!

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\KrononUser;
use Faker\Provider\ar_JO\Person;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class HelloController extends Controller
{
    public function index()
    {
        return view('hello.hello');
    }
}

④URLにアクセスして表示!!!
http://127.0.0.1:10080/hello
スクリーンショット 2021-01-10 21.45.34.png

3-9.データベースに接続してみる!!!!

ソースコード上で backend/.env のDB接続設定を修正する。
スクリーンショット 2021-01-10 21.54.15.png
①以下のコードに変更!!DBサーバー作った時のに合わせながら!

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=sg_db
DB_USERNAME=sg
DB_PASSWORD=sg

スクリーンショット 2021-01-10 21.57.32.png
②backend/.env.example も同様に変更する

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=sg_db
DB_USERNAME=sg
DB_PASSWORD=sg

ちなみにgitcloneした際はここからやれば動きます

③Laravelインストール

app コンテナにはいってからいろいろ

MacBook-Pro docker-test % docker-compose exec app bash
//コンポーザーをインストールする
root@36ffabe3ffc9:/work# composer install
//.env.exampleを.envファイルにコピーする
root@36ffabe3ffc9:/work# cp .env.example .env
//このコマンドでアプリケーションキーを生成できます。
root@36ffabe3ffc9:/work# php artisan key:generate
Application key set successfully.

//デフォルトで入っているマイグレーションを実行!!デフォルトのテーブルがDBに反映される
root@36ffabe3ffc9:/work# php artisan migrate
Migration table created successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (40.88ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (37.06ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (35.00ms)

これでデフォルトですが、DBにテーブルなどが入りました!!

④データベースの確認(デフォルト)

MacBook-Pro docker-test % docker-compose exec db bash 
//設定したUSERでmysqlにログイン
root@d94d20dd2212:/# mysql -u sg -p
//設定したPASSWORDを入力.envに書いた!!
Enter password: 
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| sg_db              |
+--------------------+
2 rows in set (0.01 sec)
mysql> use sg_db
Database changed
mysql> show tables;
+-----------------+
| Tables_in_sg_db |
+-----------------+
| failed_jobs     |
| migrations      |
| password_resets |
| users           |
+-----------------+

⑤migrationをつくってアプリに反映する!!
スクリーンショット 2021-01-11 13.51.27.png
/backend/database/migration/2021_01_03_090902_create_people_table.php
を作成

※/backend/database/migration/の中にデフォルトでいろいろ入っているからそれをコピーすればいいかも!!
以下のコードを貼り付け!

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePeopleTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('people', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('mail');
            $table->integer('age');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('people');
    }
}

⑥migrationを実行(appコンテナ)の中に入って実行!
appのなかじゃないとdockerにあるDBコンテナに届かない

MacBook-Pro docker-test % docker-compose exec app bash
root@36ffabe3ffc9:/work# php artisan migrate
Migrating: 2021_01_03_090902_create_people_table
Migrated:  2021_01_03_090902_create_people_table (49.03ms)

⑦Seederを作成する!!まずはデフォルトにあるDatabaseSeeder.phpから
"Seederとは??"
ダミーデータ。データベースにコマンドを実行するだけでデータを入れられる!!
スクリーンショット 2021-01-11 14.37.11.png
DatabaseSeeder.phpに以下のソースを貼り付け

$this->call(PeopleTableSeeder::class);

呼び出される側のPeopleTableSeeder.phpを作成
スクリーンショット 2021-01-11 14.40.27.png
PeopleTableSeeder.phpに以下のソースを貼り付け

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class PeopleTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $param = [
            'id' => 1,
            'name' => 'test',
            'mail' => 'test',
            'age' => 20,
        ];
    DB::table('people')->insert($param);
    }

}

⑧Seederを実行する!

MacBook-Pro docker-test % docker-compose exec app bash
root@36ffabe3ffc9:/work# php artisan db:seed
Seeding: Database\Seeders\PeopleTableSeeder
Seeded:  Database\Seeders\PeopleTableSeeder (29.08ms)
Database seeding completed successfully.

これでテーブルはできたし、ダミーデータもできました!あとはweb上で表示すれば完璧ですね!!

データベースに登録されているデータを表示する

①views/hello.blade.phpを作成
スクリーンショット 2021-01-11 14.49.15.png
以下のソースhello.blade.phpを変更

<html>
    <body>
        <h1>HELLO Larabel<h1>
            <h1>DBから表示しているよ<h1>
            <tr><th>Name</th><th>Mail</th><th>Age</th></tr>
                <br>
                @foreach ($items as $item)
                <tr>
                    <td>{{$item -> name}}</td>
                    <td>{{$item -> mail}}</td>
                    <td>{{$item -> age}}</td>
                </tr>
            @endforeach
    </body>
</html>

②Controllers/HelloController.phpを変更
スクリーンショット 2021-01-11 14.50.24.png
以下のソースに変更する

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\KrononUser;
use Faker\Provider\ar_JO\Person;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class HelloController extends Controller
{
    public function index()
    {
        $items = DB::table('people')->get();
        return view('hello.hello',['items' => $items]);
    }
}

③作成したアプリにアクセス!!
http://127.0.0.1:10080/hello
スクリーンショット 2021-01-11 14.53.14.png
これでDBさきほどSeederでいれたダミーデータが表示されていたら完璧ですね!!!
最後にcommmitして終了!!!

MacBook-Pro docker-test % git add .
MacBook-Pro docker-test % git commit -m "db complete"
MacBook-Pro docker-test % git push

あとはこれを世の中に出すためAWSを使えればOK!!!
次回にご期待!!

お疲れ様でした

こういった環境構築かなり疲れますよねーもうへとへとです
全体像を確認したければこちら
【ギリ初心者向け】Laravel Docker AWS(EC2) Webアプリ(PHP)を0から簡単にデプロイする方法(無料)① ー全体像編ー
https://qiita.com/SG_Sg/items/6b8ce48567b6b6602805
次回は③
【ギリ初心者向け】Laravel Docker AWS(EC2) Webアプリ(PHP)を0から簡単にデプロイする方法(無料)③ ーEC2にデプロイ編ー
を予定してます。

参考文献

【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン
https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4
↑の方の記事めちゃくちゃわかりやすいし、細かく書いてあるのでこちらも参考にしていただければ
特に最初の初期設定の情報のまとまりがすばらしいです。この記事の上位互換。

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

Laravelで 【composer install 】した時に 【command not found: composer】と表示された時

composer install時の、エラー処理方法

【状態】
Laravelをdockerで環境構築し、下記コマンドでコンテナを立ち上げた状態

docker-compose up -d --build

〜補足説明〜

-d
→デタッチド・モード: バックグラウンドでコンテナを実行するということ。

--build
→Dockerfileの再読み込みをしてくれて、更新した情報を反映してくれる。

【問題】
コンテナが立ち上がったので、 http://localhost/にアクセスすると、下記のようなエラー文が表示された!
image.png

【原因】
composer が install できていなかった、、、

【解決策】
1.Laravelが入っているディレクトリに移動

(cdでディレクトリを変更しながらappbootstrapが入っているフォルダに移ってください)

2.インストールを実行

composer install

or

composer update

で実行できます。

※上記で指定したディレクトリでないと、command not found: composerと表示されてしまうので、注意してください!!

【感想】
最近は調べる力ばかり、伸びている気がしますね、、、

There is no shame in not knowing; the shame lies in not finding out

知らないことは恥ではない。知ろうとしないことこそ恥である

と言い聞かせて、

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

crontabでdocker-compose execを実行すると"the input device is not a TTY"が出る

解決策

-Tのオプションを使う

問題があった時の設定

0 4 * * * docker-compose exec {container_name} {some_command}

解決後の設定

0 4 * * * docker-compose exec -T {container_name} {some_command}

原因

よくはわかっていないが、docker-compose execはデフォルトで擬似ttyが割り当てられてしまう。
それを解除するオプションが -Tとのこと。

-T
Disable pseudo-tty allocation. By default docker-compose execallocates a TTY.

参考

https://docs.docker.com/compose/reference/exec/

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

【これを知りたかった】Docker+RailsでRSpec導入〜トップページ表示確認テストの書き方まで

背景

  • Railsのポートフォリオ作成で、RSpecの導入を検討
  • 導入から、トップページの表示を確認するテストの書き方までをまとめました

1. Gemのインストール

GemfileにRails用のrspecとfactory_botのgemを加える

Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
end

Dockerでbuildし直す

docker-compose build

2. rspecの導入

以下のコマンドでRailsアプリに、rspecを導入

$ bundle exec rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

3. rspecの生成ファイルの定義

  • 設定ファイルはconfig/application.rb
  • falseをつけると不要なテストファイルは作成されなくなる
config/application.rb
config.generators do |g|
  g.test_framework :rspec, 
        # 不要なテストファイルは以下のようにfalseをつけて書く
        view_specs: false, 
        helper_specs: false, 
        controller_specs: false, 
        routing_specs: false
end

4. Viewをテストしてみる

Viewテスト用のrspec作成

rails g rspec:view Homes

ただ、これだけだと/spec/views/homes/というフォルダしか作成されない

テスト用のファイル作成

/spec/views/homes/以下にindex.html.erb_spec.rbを作成し、以下を記述

/spec/views/homes/index.html.erb_spec.rb
require 'rails_helper'

RSpec.describe "homes/index", type: :view do
  it 'should display top page' do
    visit "/"
    expect(page).to have_content 'こされ'
  end
end

以下補足
* visit "/" : ルートに遷移
* expect(page).to have_content '文字列' : 現在の表示ページ上に"文字列"があることを確認

テスト実行

$ rspec

homes/index
  should display top page

Finished in 24.64 seconds (files took 7.02 seconds to load)
1 example, 0 failures

0 failuresなので、テストが成功

RSpecで「NoMethodError: undefined method `visit' for ~ 」と怒られた時

spec/spec_helper.rb
...

require 'capybara/rspec'

RSpec.configure do |config|
  config.include Capybara::DSL # 追記

...

参考

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

Railsデプロイ with Docker

参考サイト

Docker超入門 :きよとのプログラミング大学

参照

Rails環境構築 with Docker
こちらでDockerを使用したRailsの環境構築までを行っております。

Docker 用語とコマンド
Dockerについての用語やコマンドについてはこちらをご覧ください

環境

Mac OS
docker-compose 1.27.4
heroku/7.47.7
Mysql 8.0
ruby 2.7
rails 6.1.0

heroku実装

herokuログイン

terminal.
rails_on_docker % heroku login
heroku: Press any key to open up the browser to login or q to exit:  //Enterキーでログイン
rails_on_docker % heroku container:login

herokuアプリ作成

terminal.
rails_on_docker % heroku create <rails-koumori>  // < >内は任意の名前を指定

データベース追加・設定

データベースの追加

terminal.
rails_on_docker % heroku addons:create cleardb:ignite -a rails-koumori 

本番環境の接続先情報を環境変数に修正

database.yml
production:
  <<: *default
  database: <%= ENV['APP_DATABASE_DATABASE'] %>
  username: <%= ENV['APP_DATABASE_USERNAME'] %>
  password: <%= ENV['APP_DATABASE_PASSWORD'] %>
  host: <%= ENV['APP_DATABASE_HOST'] %>

接続先情報を環境変数に設定

terminal.
rails_on_docker % heroku config -a rails-koumori
CLEARDB_DATABASE_URL: mysql://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true

rails_on_docker % heroku config:add APP_DATABASE='データベス名'-a rails-koumori
rails_on_docker % heroku config:add APP_USERNAME='ユーザー名' -a rails-koumori
rails_on_docker % heroku config:add APP_PASSWORD='パスワード' -a rails-koumori
rails_on_docker % heroku config:add APP_HOST='ホスト名' -a rails-koumori

rails_on_docker % heroku config -a rails-koumori  //登録の確認

本番環境用の記述

空のファイル作成

terminal.
rails_on_docker % touch start.sh  
start.sh
#!/bin/sh

# 本番環境
if [ "${RAILS_ENV}" ="production" ]
then
    bundle exec rails assets:precompile
fi

bundle exec rails s -p ${PORT:-3000} -b 0.0.0.0

Dockerfileの追記

Dockerfile
# ベースイメージの指定
FROM ruby:2.7

#追記
ENV RAILS_ENV=production  
.
.
.
.

#dockerにコピー
COPY start.sh /start.sh  
#実行権限を付与
RUN shmod 744 /start.sh  
#起動時に実行
CMD ["sh","/start.sh"]   

本番環境に適用

terminal.
rails_on_docker % heroku config:add RAILS_SERVE_STATIC_FILES="true" -a r
ails-koumori  //本番環境にassets:precompileを適用

rails view画面実装

コントローラー作成

terminal.
rails_on_docker % docker-compose exec web bundle exec rails g controller users 

トップページにusers/indexを指定

routes.rb
Rails.application.routes.draw do
  get '/',to: "users#index"
end

コントローラーの記述

users.controller.rb
class UsersController < ApplicationController
  def index
  end
end

index.html.erbファイルを作成し記述

src>app>views>users>index.html.erb
<h1>Hello world!</h1>

Dockerコンテナのプッシュとherokuへのリリース

Dockerイメージをビルドしコンテナにプッシュ

terminal.
rails_on_docker % heroku container:push web -a rails-koumori

herokuにコンテナをリリース

terminal.
rails_on_docker % heroku container:release web -a rials-koumori

herokuを起動しブラウザで確認

terminal.
rails_on_docker % heroku open -a rails-koumori

スクリーンショット 2021-01-10 18.13.47.png

まとめ

前回のRails構築から、今回でデプロイまで完了できました。

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

Dockerを用いて掲示板を作った1

これは何

DockerとWebアプリ作成の勉強のため,この本の第3章をDockerコンテナの上で実装してみた際のメモ.

ファイル構成

$ tree .
.
├── Dockerfile
└── testbbs
    └── WEB-INF
        ├── classes
        ├── src
        │   ├── Message.java
        │   ├── PostBBS.java
        │   └── ShowBBS.java
        └── web.xml

4 directories, 5 files

Dockerfile

Dockerfile
FROM tomcat:8.5.54-jdk11-adoptopenjdk-hotspot
WORKDIR /usr/local/tomcat/webapps/
RUN mkdir -p ./testbbs
COPY ./testbbs ./testbbs/
RUN javac -classpath $CATALINA_HOME/lib/servlet-api.jar -d ./testbbs/WEB-INF/classes ./testbbs/WEB-INF/src/*.java

Javaファイル

参考にしてる本と同じ.

Message.java
import java.util.*;

public class Message {
    public static ArrayList<Message> messageList = new ArrayList<Message>();
    String title;
    String handle;
    String message;
    Date date;

    Message (String title, String handle, String message) {
        this.title = title;
        this.handle = handle;
        this.message = message;
        this.date = new Date();
    }
}
PostBBS.java
import java.io.*;
import javax.servlet.http.*;

public class PostBBS extends HttpServlet {
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws UnsupportedEncodingException, IOException {
        request.setCharacterEncoding("UTF-8");
        Message newMessage = new Message(request.getParameter("title"),
                request.getParameter("handle"),
                request.getParameter("message"));

        Message.messageList.add(0, newMessage);
        response.sendRedirect("/testbbs/ShowBBS");
    }
}
ShowBBS.java
import java.io.*;
import javax.servlet.http.*;

public class ShowBBS extends HttpServlet {
    private String espaceHTML (String src) {
        return src.replace ("&", "&amp;").replace("<", "&lt;")
            .replace (">", "&gt;").replace ("\"", "&quot;")
            .replace ("'", "&#39;");
    }

    @Override
    public void doGet (HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>テスト掲示板</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>テスト掲示板</h1>");
        out.println("<form action='/testbbs/PostBBS' method='post'>");
        out.println("タイトル:<input type='text' name='title' size='60'>");
        out.println("<br />");
        out.println("ハンドル名:<input type='text' name='handle'>");
        out.println("<br />");
        out.println("<textarea name='message' rows='4' cols='60'></textarea>");
        out.println("<br />");
        out.println("<input type='submit' />");
        out.println("</form>");
        out.println("<hr />");

        for (Message message: Message.messageList) {
            out.println("<p> 『" + espaceHTML(message.title) + "』&nbsp; &nbsp;");
            out.println(espaceHTML(message.handle) + " さん&nbsp;&nbsp;");
            out.println(espaceHTML(message.date.toString()) + "</p>");
            out.println("<p>");
            out.println(espaceHTML(message.message).replace("\r\n", "<br />"));
            out.println("</p><hr />");
        }

        out.println("</body>");
        out.println("</html>");
    }

}

web.xmlファイル

これも本と同じ.

web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/nx/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                        http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1"
    metadata-complete="true">

    <servlet>
        <servlet-name>ShowBBS</servlet-name>
        <servlet-class>ShowBBS</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>PostBBS</servlet-name>
        <servlet-class>PostBBS</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ShowBBS</servlet-name>
        <url-pattern>/ShowBBS</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>PostBBS</servlet-name>
        <url-pattern>/PostBBS</url-pattern>
    </servlet-mapping>
</web-app>

ビルドして実行

$ docker build -t henacat .
Sending build context to Docker daemon  11.78kB

Step 1/6 : FROM tomcat:8.5.54-jdk11-adoptopenjdk-hotspot
 ---> 66317f378ae0
Step 2/6 : RUN apt-get update && apt-get install -y wget
 ---> Using cache
 ---> 871dd39a71cc
Step 3/6 : WORKDIR /usr/local/tomcat/webapps/
 ---> Using cache
 ---> 19d29e246ff6
Step 4/6 : RUN mkdir -p ./testbbs
 ---> Using cache
 ---> c2765cdc59e3
Step 5/6 : COPY ./testbbs ./testbbs/
 ---> Using cache
 ---> dbd09272e03b
Step 6/6 : RUN javac -classpath $CATALINA_HOME/lib/servlet-api.jar -d ./testbbs/WEB-INF/classes ./testbbs/WEB-INF/src/*.java
 ---> Using cache
 ---> 41e02fb5b101
Successfully built 41e02fb5b101
Successfully tagged henacat:latest

$ docker run -d -p 8080:8080 -it henacat
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND             CREATED              STATUS              PORTS                    NAMES
249799090cb1   henacat   "catalina.sh run"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp   elegant_ishizaka

http://localhost:8080/testbbs/ShowBBS にアクセスすると,ちゃんと掲示板が表示される.

Screen Shot 2021-01-11 at 2.42.20.png

ハマったこと

最初に使ったDockerイメージはtomcat:10.0.0-jdk11-adoptopenjdk-hotspot だったが,これだとコンパイルが通らなかった.
どうもサーブレットのためのパッケージが見つからないらしい.
クラスパスを間違えたかなと思い色々変えてみたがコンパイルエラーは直らず,tomcatの問題かと思いドキュメントを見にいくと,tomcat-10からどうもパッケージ名が変わったらしい.
tomcat-8 では,javax.servlet.httpだったのが,tomcat-10では,jakarta.servlet.httpとなっており,そりゃパッケージ見つからんってエラー吐かれるよなと.
と言うわけで,本と同じtomcat-8のDockerイメージを用いることで無事解決.

参考文献

前橋和弥「Webサーバを作りながら学ぶ基礎からのWebアプリケーション開発入門」技術評論社 (2016)

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

Docker(docker-compose)で、Laravelの環境を作成

初めに

もともと作られた方のものを参考(パクって)展開
こちらの方のページを参考(パク…)

こんな感じで作成

・php7.4 / mysql8 / nginx1.19 のイメージで作成
・sampleprojectというプロジェクト名で作成
・確認はDocker Desktop for Windows で確認

フォルダ構成

laravel
 ├ docker-compose.yml
 ├ docker
 │  ├ php
 │  │  ├ php.ini
 │  │  └ Dockerfile
 │  └ nginx
 │    └ default.conf
 └ server

各ファイル内容

docker-compose.yml
version: '3'

services:
  laravel_php:
    container_name: laravel_php
    build: ./docker/php
    volumes:
    - ./server:/var/www

  laravel_nginx:
    image: nginx:1.19
    container_name: laravel_nginx
    ports:
    - 80:80
    volumes:
    - ./server:/var/www
    - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
    - laravel_php

  laravel_db:
    image: mysql:8.0
    container_name: laravel_db
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: sampleproject
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: dbpass
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./docker/db/data:/var/lib/mysql
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 3306:3306
./docker/nginx/default.conf
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"
./docker/php/Dockerfile
FROM php:7.4-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y zlib1g-dev && apt-get install -y libzip-dev
RUN docker-php-ext-install pdo_mysql zip

COPY --from=composer /usr/bin/composer /usr/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www

RUN composer global require "laravel/installer"
./docker/nginx/default.conf
server {
    index index.php index.html;
    server_name localhost;
    root /var/www/sampleproject/public;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass laravel_php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

実行コマンド

1.コンテナ構築

docker-compose up -d

2.Laravelプロジェクト作成

docker exec -ti laravel_php bash

コンテナ内(バージョン6の作成コマンド)

composer create-project "laravel/laravel=6.*" sampleproject

プロジェクト作成後(コンテナ内)

cd sampleproject
chmod -R 777 storage
chmod -R 777 bootstrap/cache
php artisan serve

※localhostのみで接続できるはず

3.DB関連

.env の編集(プロジェクトフォルダ以下)

.env
DB_CONNECTION=mysql
DB_HOST=laravel_db
DB_PORT=3306
DB_DATABASE=sampleproject
DB_USERNAME=dbuser
DB_PASSWORD=dbpass

※composeのymlで記述した参考に

接続確認

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