カクカクしかじか

技術的なアレコレ

mrubyから入るシステムプログラミング入門に参加してきた

行って来た!

こちらのイベントに参加して来ました!

handsons.doorkeeper.jp

来たかったけれどキャンセルすることになってしまった方が自分の周りにいたので、可能な限りやったことを詳細に書き起こせればと思います。
なお私自身の理解の甘さで不備があるかもですが...ご容赦ください。

座学の時間

座学の時間でudzuraさんがお話されたときの資料がこちらです!
(udzuraさんのお話はホントにずっと聞いていたいww)

speakerdeck.com

ハンズオン用リポジトリと手順

01-setup.md に記載のセットアップは完了した前提

fuqda.hatenablog.com

ハンズオン用のリポジトリと各種手順はこちら

github.com

負荷を取得するmrbgemを作る

mrbgemはmruby用のgemのことだと理解しました!

ローカル(Mac)

後々のために事前にgitのconfig周りの設定を確認して設定しておく
(github.useruser.name を設定しておく)

$ vim ~/.gitconfig
$ git config --global github.user Shigeyuki-fukuda
$ git config --global user.name Shigeyuki-fukuda

作業用のworkspaceに移動し、ボイラーテンプレートをジェネレートするコマンドを打ちます!

# /Users/shigeyukifukuda/handsonsで以下実行
$ cd workspace/
$ mrbgem-template mruby-loadavg
Generate all files of mruby-loadavg
create dir : ./mruby-loadavg
create dir : ./mruby-loadavg/src
...

srcは不要っぽいので消します!

$ cd mruby-loadavg
$ rm -rf src/

Vagrantに入ってビルド出来るかチェックする

$ vagrant ssh
vagrant@ubuntu-bionic:~$
vagrant@ubuntu-bionic:~$ cd /vagrant/workspace/mruby-loadavg

# rakeコマンドを叩く
vagrant@ubuntu-bionic:/vagrant/workspace/mruby-loadavg$ rake
cd mruby && rake all MRUBY_CONFIG=/vagrant/workspace/mruby-loadavg/.travis_build_config.rb

Build summary:

================================================
      Config Name: host
 Output Directory: build/host
         Binaries: mrbc, mrbtest
    Included Gems:
             mruby-metaprog - Meta-programming features for mruby
             mruby-time - standard Time class
             mruby-io - IO and File class
             mruby-pack - Array#pack and String#unpack method
             mruby-sprintf - standard Kernel#sprintf method
             mruby-print - standard print/puts/p
             mruby-math - standard Math module
             mruby-struct - standard Struct class
             mruby-compar-ext - Enumerable module extension
             mruby-enum-ext - Enumerable module extension
             mruby-fiber - Fiber class
             mruby-enumerator - Enumerator class
             mruby-string-ext - String class extension
             mruby-numeric-ext - Numeric class extension
             mruby-array-ext - Array class extension
             mruby-hash-ext - Hash class extension
             mruby-range-ext - Range class extension
             mruby-proc-ext - Proc class extension
             mruby-symbol-ext - Symbol class extension
             mruby-random - Random class
             mruby-object-ext - Object class extension
             mruby-objectspace - ObjectSpace class
             mruby-enum-lazy - Enumerator::Lazy class
             mruby-toplevel-ext - toplevel object (main) methods extension
             mruby-compiler - mruby compiler library
             mruby-bin-mirb - mirb command
               - Binaries: mirb
             mruby-error - extensional error handling
             mruby-bin-mruby - mruby command
               - Binaries: mruby
             mruby-bin-strip - irep dump debug section remover command
               - Binaries: mruby-strip
             mruby-kernel-ext - Kernel module extension
             mruby-class-ext - class/module extension
             mruby-loadavg
             mruby-bin-mrbc - mruby compiler executable
             mruby-test - mruby test
================================================

# ./mruby/bin/mirbでmirbのコンソールを起動
vagrant@ubuntu-bionic:/vagrant/workspace/mruby-loadavg$ ./mruby/bin/mirb 
mirb - Embeddable Interactive Ruby Shell

> Loadavg.class
 => Class

今回やりたいこと

サーバーの負荷を取得するmrbgemを作ることです!
ヒントによれば、Vagrant/proc/loadavg の中身を見れば負荷が分かるそうなので見てみると...

vagrant@ubuntu-bionic:/vagrant/workspace/mruby-loadavg$ cat /proc/loadavg
0.00 0.03 0.00 1/365 2384

これを出力するmrbgemを作っていきます!

mrblibのコンソールを編集する

/handsons/mruby-loadavg/mrblib/mrb_loadavg.rb を編集します!
以下のソースは最終的なudzuraさんの解答例です。

class Loadavg
  def self.open
    self.new
  end

  def initialize
    f = File.open("/proc/loadavg", "r")
    data = f.read.chomp.split
    f.close

    @avg_over_1min = data[0].to_f
    @avg_over_5min = data[1].to_f
    @avg_over_15min = data[2].to_f
    @runnable_tasks = data[3].split('/')[0].to_i
    @existing_tasks = data[3].split('/')[1].to_i
    @last_created_pid = data[4].to_i
  end
  attr_reader :avg_over_1min,
              :avg_over_5min,
              :avg_over_15min,
              :runnable_tasks,
              :existing_tasks,
              :last_created_pid
end

自分の作業リポジトリにプッシュしておく

これは事前にやっておくと良さげ...

$ cd workspace/mruby-loadavg
$ git init .
$ git add . ; git status
$ git commit
$ git remote add origin git@github.com:Shigeyuki-fukuda/mruby-loadavg.git
$ git push origin master

上記のように実装が完了したら、ビルドして動作確認してみます!

~/h/w/mruby-loadavg (master|✔) $ vagrant ssh
# ビルド実行ディレクトリへ移動
vagrant@ubuntu-bionic:~$ cd /vagrant/workspace/mruby-loadavg

# ビルド
vagrant@ubuntu-bionic:/vagrant/workspace/mruby-loadavg$ rake

# コンソールで負荷を取得
vagrant@ubuntu-bionic:/vagrant/workspace/mruby-loadavg$ ./mruby/bin/mirb
mirb - Embeddable Interactive Ruby Shell

> Loadavg.open
 => #<Loadavg:0x562edb9f5060 @avg_over_1min=0.22, @avg_over_5min=0.07000000000000001, @avg_over_15min=0.02, @runnable_tasks=1, @existing_tasks=366, @last_created_pid=2491>

この後の発展編

こちらは手が及ばず...(割愛)

ngx_mruby を触る

vagrant@ubuntu-bionic:~$ cd ~
vagrant@ubuntu-bionic:~$ git clone https://github.com/matsumotory/ngx_mruby.git
vagrant@ubuntu-bionic:~$ cd ngx_mruby
vagrant@ubuntu-bionic:~/ngx_mruby$ vim build_config.rb

以下の指示に沿って build_config.rb を修正します。

## L32 松本さんのmruby-uname を使わないならコメントアウト
# conf.gem :github => 'matsumotory/mruby-uname'

...
## L59 以降 end までの間に、今回使うmgemを指定する
  conf.gem github: "${github id}/mruby-loadavg"
  conf.gem github: "${github id}/mruby-uname"
end

修正後がこちらです!

MRuby::Build.new('host') do |conf|

  toolchain :gcc

  conf.gembox 'full-core'

  conf.cc do |cc|
    cc.flags << ENV['NGX_MRUBY_CFLAGS'] if ENV['NGX_MRUBY_CFLAGS']
  end

  conf.linker do |linker|
    linker.flags << ENV['NGX_MRUBY_LDFLAGS'] if ENV['NGX_MRUBY_LDFLAGS']

    # when using openssl from brew
    if RUBY_PLATFORM =~ /darwin/i
      linker.flags << '-L/usr/local/opt/openssl/lib -lcrypto'
    end
  end

  #
  # Recommended for ngx_mruby
  #
  conf.gem :github => 'iij/mruby-env'
  conf.gem :github => 'iij/mruby-dir'
  conf.gem :github => 'iij/mruby-digest'
  conf.gem :github => 'iij/mruby-process'
  conf.gem :github => 'mattn/mruby-json'
  conf.gem :github => 'mattn/mruby-onig-regexp'
  conf.gem :github => 'matsumotory/mruby-redis'
  conf.gem :github => 'matsumotory/mruby-vedis'
  conf.gem :github => 'matsumotory/mruby-userdata'
  # conf.gem :github => 'matsumotory/mruby-uname'
  conf.gem :github => 'matsumotory/mruby-mutex'
  conf.gem :github => 'matsumotory/mruby-localmemcache'
  conf.gem :mgem => 'mruby-secure-random'

  # ngx_mruby extended class
  conf.gem './mrbgems/ngx_mruby_mrblib'
  conf.gem './mrbgems/rack-based-api'
  conf.gem './mrbgems/auto-ssl'

  # use memcached
  # conf.gem :github => 'matsumotory/mruby-memcached'
  
  # build error on travis ci 2014/12/01, commented out mruby-file-stat
  # conf.gem :github => 'ksss/mruby-file-stat'

  # use markdown on ngx_mruby
  # conf.gem :github => 'matsumotory/mruby-discount'

  # use mysql on ngx_mruby
  #conf.gem :github => 'mattn/mruby-mysql'

  # have GeoIPCity.dat
  # conf.gem :github => 'matsumotory/mruby-geoip'

  # Linux only for ngx_mruby
  # conf.gem :github => 'matsumotory/mruby-capability'
  # conf.gem :github => 'matsumotory/mruby-cgroup'
  conf.gem '/vagrant/mruby-loadavg'
  conf.gem '/vagrant/mruby-myuname'
end

MRuby::Build.new('test') do |conf|
  # load specific toolchain settings

  # Gets set by the VS command prompts.
  if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
    toolchain :visualcpp
  else
    toolchain :gcc
  end

  enable_debug

  conf.gem :github => 'matsumotory/mruby-simplehttp'
  conf.gem :github => 'matsumotory/mruby-httprequest'
  conf.gem :github => 'matsumotory/mruby-uname'
  conf.gem :github => 'matsumotory/mruby-simpletest'
  conf.gem :github => 'mattn/mruby-http'
  conf.gem :github => 'mattn/mruby-json'
  conf.gem :github => 'iij/mruby-env'

  # include the default GEMs
  conf.gembox 'full-core'
end

ビルドしていく

vagrant@ubuntu-bionic:~/ngx_mruby$ env NGINX_CONFIG_OPT_ENV='--prefix=/usr/local/nginx-mruby' sh ./build.sh
...
ngx_mruby building ... Done
build.sh ... successful

vagrant@ubuntu-bionic:~/ngx_mruby$ sudo make install

vagrant@ubuntu-bionic:~/ngx_mruby$ /usr/local/nginx-mruby/sbin/nginx -V

nginx version: nginx/1.17.1

上手くいかない場合の救済措置

リモートの自分のリポジトリにプッシュしていない人はこちらを追加します。 自分はプッシュしたにも関わらずなぜか上手くいかなかったので、以下のgemを使用する方にしたら上手くいきました...

# conf.gem github: "Shigeyuki-fukuda/mruby-loadavg"
# conf.gem github: "Shigeyuki-fukuda/mruby-uname"
# 上二つの設定では何故かダメだったので下の設定をする↓

conf.gem '/vagrant/mruby-loadavg'
conf.gem '/vagrant/mruby-myuname'

Hello, worldしてみる

ファイルを編集

vagrant@ubuntu-bionic:~/ngx_mruby/mruby$ sudo vi /usr/local/nginx-mruby/conf/nginx.conf
nginx.conf
...
    server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        # この下を追加
        location /mruby {
            mruby_content_handler_code '
              Nginx.rputs "Hello #{Nginx.module_name}/#{Nginx.module_version} world!\n"
            ';
        }
....

起動と確認

vagrant@ubuntu-bionic:~/ngx_mruby/mruby$ sudo /usr/local/nginx-mruby/sbin/nginx
vagrant@ubuntu-bionic:~/ngx_mruby/mruby$ curl http://localhost/mruby
Hello ngx_mruby/2.1.5 world!

停止

本番では絶対にやっちゃダメなので注意...w

vagrant@ubuntu-bionic:~/ngx_mruby/mruby$ sudo killall nginx

upstreamをホスト名から動的に変更する

vagrant@ubuntu-bionic:~/ngx_mruby/mruby $ sudo docker run -ti -d -p8081:80 httpd:2.4
vagrant@ubuntu-bionic:~/ngx_mruby/mruby $ sudo docker run -ti -d -p8082:80 nginx:1.16

Nginx側の設定をいじる

nginx.conf
...
    server {
        listen       80;
        server_name  localhost;
        location / {
            resolver              8.8.8.8;
            # 絶対パスで指定することに注意
            mruby_set $backend    /home/vagrant/ngx_mruby/mruby/step1.rb

            proxy_http_version    1.1;
            proxy_pass            http://$backend;
            proxy_set_header      Host $host;
            proxy_set_header      Connection "";
            proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header      X-Forwarded-Host $host;
            proxy_set_header      X-Forwarded-Server $host;
            proxy_set_header      X-Real-IP $remote_addr;
            # root   html;
            # index  index.html index.htm;
        }
    }

mrubyを作成

/usr/local/nginx-mruby/mruby/step1.rb

data = Nginx::Var.new
domain = data.http_host
upstream = ""

case domain
when /apache/
  upstream = "127.0.0.1:8081"
when /nginx/
  upstream = "127.0.0.1:8082"
else
end

upstream

ローカルでcurl叩くと...

# サブドメがapacheだと
~/h/workspace (master|✔) $ curl http://apache.127.0.0.1.xip.io:8090/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

# サブドメがnginxだと
~/h/workspace (master|✔) $ curl http://nginx.127.0.0.1.xip.io:8090/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

# 存在しないサブドメインを叩くと
~/h/workspace (master|✔) $ curl http://udzura.127.0.0.1.xip.io:8090/
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the error log for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>

現在のシステムの負荷状態を出す(今日やりたかったこと)

nginx.conf
...
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }

        location /status.json {
            # step2.rbのパス指定は絶対パスで行うこと
            mruby_content_handler /home/vagrant/ngx_mruby/mruby/step2.rb;
            add_header Content-Type application/json;
        }
    }

/usr/local/nginx-mruby/mruby/step2.rb

lav = Loadavg.new
uname = Uname.new

Nginx.rputs({
  "1min" => lav.avg_over_1min,
  "5min" => lav.avg_over_5min,
  "15min" => lav.avg_over_15min,
  "linux_nodename" => uname.nodename,
  "linux_release" => uname.release
}.to_json)

実行してみる

vagrant@ubuntu-bionic:~/ngx_mruby$ sudo apt install jq
Reading package lists... Done
Building dependency tree
Reading state information... Done
jq is already the newest version (1.5+dfsg-2).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

# サーバー負荷の取得に成功!
vagrant@ubuntu-bionic:~/ngx_mruby$ curl -s localhost/status.json | jq .
{
  "1min": 0,
  "5min": 0.03,
  "15min": 0,
  "linux_nodename": "ubuntu-bionic",
  "linux_release": "4.15.0-54-generic"
}

この後の発展編まで終えることが出来なかったのでそちらは割愛します(Cが出来るようになりたい...)

まとめ

総じてめちゃくちゃタメになるハンズオンでした!
普段nginxを仕事でいじることがないのものの、今回を機にシステムプログラミングにもチャレンジしていきたいと思いました!!!
udzuraさん!本当に貴重な機会をくださり有難うございました。