NGINX Unitを試してみたお話。

なんか、NGINXが「nginx Unit」ってのを発表してたので、何だこいつは?という感覚で触ってみました。

NGINX Unitとは?

NGINX Unit is a dynamic web and application server, designed to run applications in multiple languages. Unit is lightweight, polyglot, and dynamically configured via API. The design of the server allows reconfiguration of specific application parameters as needed by the engineering or operations.

複数の言語で書かれたアプリケーションの構成をAPIを通じて動的に変更できるウェブ・アプリケーションサーバという感じですかね。

特徴

  • Fully dynamic reconfiguration using RESTful JSON API
  • Multiple application languages and versions can run simultaneously
  • Dynamic application processes management (coming soon)
  • TLS support (coming soon)
  • TCP, HTTP, HTTPS, HTTP/2 routing and proxying (coming soon)

REST API経由で、動的に構成を再設定できたり、複数の言語で書かれたアプリケーションを同時に動かすことができる、といった感じです。将来的には、プロセス数の管理?、TLCTCP, HTTP, HTTPS, HTTP/2のルーティングやプロキシができるようにしたいらしい(今は、あるポートにアプリケーションを結びつけるだけ)。

http://unit.nginx.org/

インストール手順

検証では、ubuntuで行いました。また、phpもインストールしました。

$ wget https://nginx.org/keys/nginx_signing.key
$ sudo apt-key add nginx_signing.key
$ sudo echo 'deb https://packages.nginx.org/unit/ubuntu/ xenial unit' >> /etc/apt/sources.list.d/unit.list
$ sudo echo 'deb-src https://packages.nginx.org/unit/ubuntu/ xenial unit' >> /etc/apt/sources.list.d/unit.list
$ sudo apt update
$ sudo apt install unit
$ sudo apt install php unit-php

Installation — NGINX Unit

試してみる

1. ファイルを用意する

$ mkdir {php,php2}
$ echo '<?php echo "Hello world!";' >> php/index.php
$ echo '<?php echo "Hello world 2!";' >> php2/index.php

この状態で、unitdを起動させます。

$ sudo unitd
2018/04/17 22:56:10 [info] 1326#1326 unit started

ここから、設定を追加していきたいと思います。

$ sudo curl -X PUT -d '{"listeners":{"*:8300":{"application":"php"}},"applications":{"php":{"type":"php","root":"/home/user/php","index":"index.php"}}}' --unix-socket /var/run/control.unit.sock http://localhost
{
    "success": "Reconfiguration done."
}

JSONで送るので、シェルでぽんってやるものじゃないですし、絶対にラッパーが直ぐにできると思うのですが、こんなコンフィグを入れると、設定ができます。

$ curl localhost:8300
Hello world!
$ curl localhost:8400
curl: (7) Failed to connect to localhost port 8400: Connection refused

ここから、動的に設定を追加したいと思います。

$ sudo curl -X PUT -d '{"type":"php","root":"/home/user/php2","index":"index.php"}' --unix-socket /var/run/control.unit.sock http://localhost/applications/php2
$ sudo curl -X PUT -d '{"application":"php"}' --unix-socket /var/run/control.unit.sock 'http://localhost/listeners/*:8400'

これで、localhost:8400にアクセスすると、

$ curl localhost:8300
Hello world!
$ curl localhost:8400
Hello world 2!

そして、

$ sudo curl -X DELETE --unix-socket /var/run/control.unit.sock 'http://localhost/listeners/*:8300'

とすると、

$ curl localhost:8300
curl: (7) Failed to connect to localhost port 8300: Connection refused
$ curl localhost:8400
Hello world 2!

まだ把握しきれてない部分

  • 設定を永続化する方法(/var/lib/unitにconf.jsonがあるが、そこに永続化されているっぽい?)
  • REST APIへの送信の仕方(ちょくちょくメソッドだったり、URLを迷ったりする)

感想

割と簡単に動的に構成を変更できるアプリケーションサーバをいじることができたので、機会があれば使ってみたいと思います。

Three.jsで遊んでみたお話。

とりあえず、まずは、three.jsの公式のドキュメントに従って、Hello World的な何かをした。

npm(yarn)から読み込む方法や、ソースコードをダウンロードして読み込む方法、CDNを利用する方法があるが、そこらへんは適宜やればいいと思う。

WebGLが使えるかどうかは、以下のソースコードを読み込めばいいようにできている(さすが)。

if (Detector.webgl) {
    // Initiate function or other initializations here
    animate();
} else {
    var warning = Detector.getWebGLErrorMessage();
    document.getElementById('container').appendChild(warning);
}

github.com

それで、Hello World相当のものがこれ。

<html>
    <head>
        <title>My first three.js app</title>
        <style>
            body { margin: 0; }
            canvas { width: 100%; height: 100% }
        </style>
    </head>
    <body>
        <script src="js/three.js"></script>
        <script>
            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

            var renderer = new THREE.WebGLRenderer();
            renderer.setSize( window.innerWidth, window.innerHeight );
            document.body.appendChild( renderer.domElement );

            var geometry = new THREE.BoxGeometry( 1, 1, 1 );
            var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
            var cube = new THREE.Mesh( geometry, material );
            scene.add( cube );

            camera.position.z = 5;

            var animate = function () {
                requestAnimationFrame( animate );

                cube.rotation.x += 0.1;
                cube.rotation.y += 0.1;

                renderer.render(scene, camera);
            };

            animate();
        </script>
    </body>
</html>

これをブラウザで開くと、黒い画面の中央で緑色のcubeが回転するのが見れるはず。

直線を引く

直線を引くためには、次のような手順を経て描画する。

  1. 描画したい対象の見た目を指定するmaterialを定義する。
  2. 描画したい対象の形を指定するgeometryを定義する。
  3. それらを用いて、lineを定義する。
  4. sceneにlineを追加する。
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

const camera = new THREE.PerspectiveCamera(45, width/height, 1, 500);
camera.position.set(0,0,100);
camera.lookAt(new THREE.Vector3(0, 0, 0));

const scene = new THREE.Scene();

const material = new THREE.LineBasicMaterial( { color: 0xffffff } );

const geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3( -10, 0, 0));
geometry.vertices.push(new THREE.Vector3(  0, 10, 0));
geometry.vertices.push(new THREE.Vector3(  10, 0, 0));
geometry.vertices.push(new THREE.Vector3(  0, -10, 0));
geometry.vertices.push(new THREE.Vector3( -10, 0, 0));

const line = new THREE.Line(geometry, material);

scene.add(line);

renderer.render(scene, camera);

とりあえず、公式のドキュメントだとこのくらいで止まっているので、ここからは他の資料も使いながら。

Rendererについて

毎回はじめに実行しているTHREE.WebGLRendererは、以下のファイルで定義されている。具体的には、Canvasを用意し、WebGLのContextを用意し、適宜メソッドを追加しているという感じだ。

github.com

Rendererという名前の通り、与えられたワールドの情報(Scene)と視点の情報(Camera)から、Canvasに書き出すべき画像を計算し、Canvasに描画する。WebGLRendererは、バックエンドにWebGLを使うタイプだが、SVGRenderer というのもあり、こちらはSVGとして描画する。

THREE.WebGLRenderer()

const renderer = THREE.WebGLRenderer();

Renderer#setSize(width, height)

Canvasのサイズを指定するメソッド。

Scene

次に用意するものはSceneだが、ここで定義されている。が、実質Object3Dなので、Object3Dを見ると、どんなことが行われているのか分かる。おそらくだが、Object3Dにaddが定義されているので、他のObject3D(立方体など)の子要素として、Objectをaddすることもできるような気がする。Object3Dでは、そのオブジェクトの位置や回転具合、スケーリングなどの情報を持っている。

github.com

github.com

THREE.Scene()

const scene = new THREE.Scene();

Scene#add(objects)

Sceneにobjectsを追加する。正確には、ObjectsのparentにSceneのObjectを追加して、SceneのObjectのchildrenにObjectsを追加する。

Camera

フィールドが用意されても、描画するためのカメラがなければ映し出すことはできない。そのために、カメラを用意する必要がある。CameraもObject3Dなので、Object3Dのメソッドが使える。

THREE.PerspectiveCamera(fov, aspect, near, far)

透視投影カメラ。4要素を指定してカメラの描画を指定するのだが、結構癖がある。StackOverflowでも質問されていて、図が非常にわかりやすいので、引用させていただいた。緑・赤・紫色の線で囲まれた領域が実際にCanvasに描画される。field of view(fov)はy軸方向の視野角。aspectは縦横比。nearは自分から見える最も近い平面までの距離。farは自分から見える最も遠い平面までの距離。

const camera = THREE.PerspectiveCamera(fov, aspect, near, far);

f:id:proelbtn:20180325065659p:plain

stackoverflow.com

THREE.CubeCamera(near, far, cubeResolution)

六面カメラ(だと思う)を作成する。

THREE.OrthographicCamera(left, right, top, bottom, near, far)

平行投影カメラ。

THREE.StereoCamera()

2つの透視投影カメラ。3Dアナグリフなどを行う際に使われるらしい。人の目と同じようなカメラってことのはず。

Object3D

THREE.js内部で用いられている汎用の3Dオブジェクトを表す型。

Object3D#rotateXYZ

[XYZ]軸を回転の軸としてangle度回転させる。

Object3D#rotateOnAxis(axis, angle)

axisを回転の軸としてangle度回転させる。

Object3D#transformXYZ

[XYZ]軸を基準の軸としてdistanceだけ移動させる。

Object3D#transformOnAxis(axis, distance)

axisを基準の軸としてdistanceだけ移動させる。

Object3D#lookAt(x, y, z)

(x, y, z)の方向を向くように、物体を回転させる。

Object3D#add(objects)

objectsを自身の子要素にする。

Object3D#remove(objects)

objectsを自身の子要素じゃなくする。

Object3D#position.[xyz]

座標を保持している変数

「ルーター自作で分かるパケットの流れ」でRust入門してみたお話、その1。

そのままだと長いので、何回かに分けていきます。

なぜRustなのか

Twitterのフォロワーたちがめっちゃ「Rustいいぞ」と言ってくるので、とりあえず試したみたいなと思って手頃な対象を見つけたから。調べてみると、結構扱いやすそうなデータリンク層の操作するライブラリがあったので、それを使ってRustに入門したいと思います。

準備

ソースコードへの追記

Cargoでプロジェクトを作成したら、libpnetというライブラリを使うために、Cargo.tomlに追記します。

[dependencies.pnet]
version = "0.21.0"

github.com

github.com

仮想ネットワークデバイスについて

手元でルーターの実験をするには、Linuxの基本的な仮想ネットワークについてもおさらいしておいたほうがいいかなと思います。まぁ、前提知識がある人じゃないと見てもわからないと思いますが。。。

netns

netnsはネットネームスペースのことです。まぁ、よくnetnsはL3のスイッチとかルーターとかって解説されることも多いですが、それのためには、適切にveth間のパケット転送をしてくれる何かしら(iptablesだったり、これから作成するルータだったり)が必要になるのでちょっと説明が下手だと思ってたりもするんですが。まずはじめにちゃんとネームスペースを分離する方法からやっていきたいと思います。

# add
sudo ip netns add [name]

# list
sudo ip netns list

# delete
sudo ip netns delete [name]

# exec
sudo ip netns exec [name] [cmd]

# join interface
sudo ip link set [name-device] netns [name]

veth

vethは仮想のLANケーブルのイメージです。

# add
sudo ip link add name [name1] type veth peer name [name2]

# delete
sudo ip link delete name [name1]

linux bridge

linux bridgeは仮想のL2ブリッジです。ipコマンドからも作成できるのですが、ipコマンドからはvethをattachする方法がわからないので、brctlを使っていきたいと思います。

# add
sudo brctl addbr [name]

# delete
sudo brctl delbr [name]

# attach interface
sudo brctl addif [name] [name-veth]

# detach interface
sudo brctl delif [name] [name-veth]

libpnet

libpnetはrustのクロスプラットフォームな低レイヤーなネットワークのライブラリです。本来、こいつ自体がパケットの解析もできるはずなのですが、その機能はあえて車輪の再発明をしていきます。

第2章 リンクレイヤープログラミングの基本

この章では、ざっくり言ってしまうとEthernetヘッダの解析をするという感じなので、そこまで作っていきたいと思います。まずは、環境を作っていきたいと思います。

sudo ip netns add ns1
sudo ip netns add ns2

sudo ip link add name ns1-veth type veth peer name ns2-veth

sudo ip link set ns1-veth netns ns1
sudo ip link set ns2-veth netns ns2

sudo ip netns exec ns1 ip link set ns1-veth up
sudo ip netns exec ns1 ip addr add 10.0.0.1/24 dev ns1-veth
sudo ip netns exec ns2 ip link set ns2-veth up
sudo ip netns exec ns2 ip addr add 10.0.0.2/24 dev ns2-veth

ここまでコマンドを実行して、ちゃんとpingが通ればOKです。

sudo ip netns exec ns1 ping 10.0.0.2

そして、ここでプログラムをns2内で実行する。

sudo ip netns exec ns2 ./target/debug/router

github.com

すると、こんな具合に、パケットがu8の配列として見れると思います。

$ cargo build && sudo ip netns exec ns2 ./target/debug/router
   Compiling router v0.1.0 (file:///home/proelbtn/Workspaces/Rust/router)
    Finished dev [unoptimized + debuginfo] target(s) in 0.66 secs
[82, 52, 83, 131, 223, 215, 110, 150, 55, 154, 213, 138, 8, 0, 69, 0, 0, 84, 214, 243, 64, 0, 64, 1, 79, 179, 10, 0, 0, 1,10, 0, 0, 2, 8, 0, 206, 70, 36, 125, 0, 1, 21, 145, 183, 90, 0, 0, 0, 0, 116, 124, 5, 0, 0, 0, 0, 0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]
[110, 150, 55, 154, 213, 138, 82, 52, 83, 131, 223, 215, 8, 0, 69, 0, 0, 84, 190, 43, 0, 0, 64, 1, 168, 123, 10, 0, 0, 2, 10, 0, 0, 1, 0, 0, 214, 70, 36, 125, 0, 1, 21, 145, 183, 90, 0, 0, 0, 0, 116, 124, 5, 0, 0, 0, 0, 0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]
[110, 150, 55, 154, 213, 138, 82, 52, 83, 131, 223, 215, 8, 6, 0, 1, 8, 0, 6, 4, 0, 1, 82, 52, 83, 131, 223, 215, 10, 0, 0, 2, 0, 0, 0, 0, 0, 0, 10, 0, 0, 1]
[82, 52, 83, 131, 223, 215, 110, 150, 55, 154, 213, 138, 8, 6, 0, 1, 8, 0, 6, 4, 0, 1, 110, 150, 55, 154, 213, 138, 10, 0,0, 1, 0, 0, 0, 0, 0, 0, 10, 0, 0, 2]
[110, 150, 55, 154, 213, 138, 82, 52, 83, 131, 223, 215, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 82, 52, 83, 131, 223, 215, 10, 0, 0, 2, 110, 150, 55, 154, 213, 138, 10, 0, 0, 1]
[82, 52, 83, 131, 223, 215, 110, 150, 55, 154, 213, 138, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 110, 150, 55, 154, 213, 138, 10, 0,0, 1, 82, 52, 83, 131, 223, 215, 10, 0, 0, 2]

これから、この数値を順番にパースしていく感じですね。。。若干だるそう。

家のWi-Fiの環境をRaspberry Pi 3BとLEDEで構築し直したお話。

経緯

昔、私はApple信者だったので、macbook + iPhone6 + AirMac Time Capsuleで家庭内のネットワークを賄っていたのですが、今やmacbookにはLinuxが入っているし、AirMac Time CapsuleDHCPのためだけにいるような構成になってきたので、そろそろAirMac Time Capsuleさんにも消えてもらおうと思ってちょっと頑張りました。

今までの状況

なぜRaspberry Piなのか

まったく関係ないOpen vSwitch用のルータを作るという試みでOpenWRTに触れたのですが、非常に扱いやすくAirMac Time Capsuleをリプレースするのにちょうどいいと思いました。ですが、わざわざそのために1台ルータを買うのも馬鹿らしかったので、床に落ちていたRaspberry Piくんでできないかと調べてみたところサポートされているということで試してみました。

ですが、後から下のようなメリットが見えてきました。

  • 普通のルータよりも性能がいい
  • SDカードにイメージを焼くので、失敗しても何度でも簡単にやり直せる(シリアルを取ってどうこうしなくてもいい)

手順

1. Raspberry PiにLEDEを入れる。

はじめはOpenWRTを入れようと思ったのですが、動かないので調べて見たところ、フォーラムに同じような原因でOpenWRTが動いていない方がいらっしゃったので、LEDEを入れることにしました。というのも、まだOpenWRT/LEDE周りに弱く違いがわかっていないというのが一番大きかったりするのですが、できることは(Webのコンソールから見た感じ)だいたい同じっぽいのでいいかなということになりました。

How install LEDE/OpenWRT On Raspberry Pi 3 Model B ? - Raspberry Pi Forums

  1. こちらのリンクから、bcm2710-rpi-3-ext4-sdcard.img.gzをダウンロードしてくる。
  2. ダウンロードされたファイルはgzipで圧縮されているので、解凍する
  3. Raspberry Piに挿すmicroSDにそのファイルを書き込む(Linuxならddあたりを使って)
  4. Raspbeery Piを起動させる。
$ wget https://downloads.lede-project.org/releases/17.01.0/targets/brcm2708/bcm2710/lede-17.01.0-r3205-59508e3-brcm2708-bcm2710-rpi-3-ext4-sdcard.img.gz
$ gzip -d lede-17.01.0-r3205-59508e3-brcm2708-bcm2710-rpi-3-ext4-sdcard.img.gz
$ sudo dd if=lede-17.01.0-r3205-59508e3-brcm2708-bcm2710-rpi-3-ext4-sdcard.img of=/dev/[SDカードのデバイスファイル] bs=1M

この段階で、Raspberry Piの有線ポートには192.168.1.1/24が振られているので、そこと自分のパソコンをLANケーブルでつなぎ、適当なIPアドレスを割り当てる。

$ sudo ip addr add 192.168.1.2/24 dev [Ethernetのデバイス名]

ここで、192.168.1.1にpingが打って帰ってくるのを確かめておくといいと思います。

2. LEDEを設定していく

ここは、行った変更点だけざっくりと。

System

System Properties

  • Timezone => Asia/Tokyo

Time Synchronization

  • NTP server candidates => ntp.nict.jp

Interfaces - LAN

Common Configuration

  • IPv4 address => 192.168.0.253
  • IPv4 netmask => 255.255.255.0
  • IPv4 gateway => 192.168.0.254
  • IPv4 broadcast => 192.168.0.255
  • Use custom DNS servers => 8.8.8.8 8.8.4.4
  • IPv6 assignment length => disabled

DHCP Server

  • Ignore interface => yes
  • Router Advertisement-Service => disabled
  • DHCPPv6-Service => disabled
  • NDP-Proxy => disabled

Wireless Network: Master "26d569911aca8c49e7424a45818e091c" (wlan0)

Device Configuration

  • Transmit Power => 10 dBm (10 mW)
  • Country Code => JP - Japan

Interface Configulation

  • ESSID => 26d569911aca8c49e7424a45818e091c
  • Mode => Access Point
  • Hide ESSID => yes
  • Encryption => WPA2-PSK
  • Key => [Wi-Fiのパスワード]
  • MAC-Address Filter => Allow listed only
  • MAC-List => [自分のMACアドレス]

今後の目標

全然メモリも食ってないし、SSIDも複数提供できそうなので、もっとガツガツと設定していきたいと思います。

追記

そろそろネットワークが複雑になってきたので、一回整理しようかなと思ってます。。。

Ansibleの自分向けの備忘録を作ったお話。

備忘録なので適当ですが。。。

ディレクトリ構成

Best Practices — Ansible Documentationがすごく良いと思っている。

production
staging

group_vars/
   group1                 # グループごとの変数
   group2
host_vars/
   hostname1              # ホストごとの変数
   hostname2             

site.yml                  # 親のプレイブック
webservers.yml            # 子供のプレイブック
dbservers.yml             # 子供のプレイブック

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies
        library/          # roles can also include custom modules
        module_utils/     # roles can also include custom module_utils
        lookup_plugins/   # or other types of plugins, like lookup in this case

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""
    fooapp/               # ""

よく使うモジュール

template

- template:
    src: /mytemplates/foo.j2
    dest: /etc/file.conf
    owner: bin
    group: wheel
    mode: 0644

apt

- name: Install the version '1.00' of package "foo"
  apt:
    name: foo=1.00
    state: present

- name: Install latest version of "openjdk-6-jdk" ignoring "install-recommends"
  apt:
    name: openjdk-6-jdk
    state: latest
    install_recommends: no

- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
  apt:
    update_cache: yes
    cache_valid_time: 3600

- name: Install a .deb package from the internet.
  apt:
    deb: https://example.com/python-ppq_0.1-1_all.deb

- name: Remove useless packages from the cache
  apt:
    autoclean: yes

- name: Remove dependencies that are no longer required
  apt:
    autoremove: yes

GitBookでMermaidを使えるようにしたお話。

GitBookでちょっとシーケンス図を書きたくなったので、拡張を書いてみた。

gitlab.com

GitBookには、Pluginの機能があり、そもそも拡張なんか書かなくても簡単にMermaidくらいなら追加することができるようになってはいる(https://plugins.gitbook.com/browse?q=mermaid)。だが、それだと後から自分で拡張したくなったときにできなくなってしまうので、訓練も兼ねて書いてみた。

具体的には、Markdown中にある+ tagを見つけたら、その後にあるまでの内容をconに、それより前の内容をpreに、それより後の内容をpostとして、後から定義するfuncに渡しているだけ。同じ方法を使って、コメントだったりを実装できると思う。

追記

コメント拡張を追加した。

gitlab.com

たった3行で新しいコードブロックが実装できるのは非常に良い。