ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

fusuma/mruby-fuseでFUSEを簡単に試す

mruby Advent Calendar 2016 の 2日目の記事です。 昨日は おごもりさんのRuby Miniature Book の記事でした。

今日は、最近こそこそ作っているfusumaとmruby-fuseが、なんとなく面白いおもちゃにはなってきたので、せっかくということで紹介したいと思います。

fusuma (FUSe Upon Mruby-script Assistance)

github.com

mrubyとFUSEを用いて、ファイルシステムを作れるコマンドベースのミドルウェアです。

インストールはx86_64なLinuxバイナリは用意してありますのでひとまずそちらを解凍し、パスの通るところに配置すればOKです。各ディストロで fuse/ilbfuse 相当と libcurl 相当を別途インストールす必要があるでしょう。

とりあえず、libfuseの hello.c に対応するような簡単なファイルシステムクラスを実装してみます。こんな感じで。

FUSE.program_name = "fusuma"
FUSE.path = "/tmp/foo"
FUSE.fsname = "fusuma"
FUSE.subtype = "fusuma"
FUSE.uid = FUSE.gid = 1000

FileStat = Struct.new(:st_mode, :st_nlink, :st_size)

class Example
  # This is called just before on_getattr
  def initialize(path, *a)
    @path = path
    case @path
    when "/hello"
      @value = "Hello, mruby fuse!!\n"
    when "/world"
      @value = "Hello, yet another mruby fuse!!\n"
    end
  end

  def on_getattr
    case @path
    when "/"
      return FileStat.new(FUSE::S_IFDIR|0755, 2, nil)
    when "/hello"
      return FileStat.new(FUSE::S_IFREG|0444, 1, @value.size)
    when "/world"
      return FileStat.new(FUSE::S_IFREG|0644, 1, @value.size)
    else
      return nil
    end
  end

  def on_open
    if ["/hello", "/world"].include? @path
      return 0
    else
      return nil
    end
  end

  def on_readdir
    return nil if @path != "/"
    return ["hello", "world"]
  end

  def on_read_all
    return nil if @path == "/"
    return [@value, @value.size]
  end

  def on_truncate(size)
    @value = ""
    return 0
  end

  def on_write(buf, offset)
    @value << buf
    return buf.size
  end
end

FUSE.run Example

このRubyスクリプトfusuma コマンドで実行します。すると、ターミナルに張り付きます。

$ mkdir /tmp/foo
$ fusuma example.rb

別のセッションから確認すると、ちゃんと fuse としてマウントされていて、また、ファイルやディレクトリを認識していることがわかります。

$ mount | grep fusuma
fusuma on /tmp/foo type fuse.fusuma (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000,default_permissions)

続いて、ファイルに書き込むとSlackに通知するような謎ファイルシステムを作ってみます。全体はこんな感じで。

FileStat = Struct.new(:st_mode, :st_nlink, :st_size)

class SlackFS
  def initialize(path, hook_url)
    @path = path
    @hook_url = hook_url
  end

  def on_getattr
    case @path
    when "/"
      return FileStat.new(FUSE::S_IFDIR|0755, 2, nil)
    when "/notify"
      return FileStat.new(FUSE::S_IFREG|0222, 1, 0)
    else
      return nil
    end
  end

  def on_open
    @path != "/slack" ? 0 : nil
  end

  def on_readdir
    return nil if @path != "/"
    return ["notify"]
  end

  def on_write(message, offset)
    req = HTTP::Request.new
    req.method = "POST"
    req.body = <<EOJ
payload={"text":#{message.inspect}}
EOJ

    req.headers['Content-Type'] = "application/x-www-form-urlencoded"
    res = Curl.new.send(@hook_url, req)
    puts "Post: #{res.body} / #{res.status_code}"

    return message.size
  end
end

FUSE.program_name = "fusuma"
FUSE.path = "/dev/slack"
FUSE.fsname = "slackfs"
FUSE.subtype = "slackfs"
FUSE.uid = FUSE.gid = 1000

FUSE.run SlackFS, ENV["SLACK_INCOMING_HOOK"]

マウント先を準備し、これをマウントすると、 /dev/slack/notify と言うファイルが...

$ sudo mkdir /dev/slack
$ sudo chown vagrant: /dev/slack
$ export SLACK_INCOMING_HOOK="https://hooks.slack.com/services/..."
$ ./mruby/bin/fusuma sample/slackfs.rb &
$ mount | grep slack 
slackfs on /dev/slack type fuse.slackfs (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000,default_permissions)
$ ls -l /dev/slack/
total 0
--w--w--w- 1 vagrant vagrant 0 Dec 31  1969 notify

書き込んでみましょう

$ echo 'I :heart: mruby system programming!' > /dev/slack/notify

f:id:udzura:20161202154902p:plain

なんだかよくわからないですけど便利そうなファイルシステムですね!

fusuma の設計思想

内部的には、libfuseの struct fuse_operations に対応するような各フックを、Rubyのクラスでインスタンスメソッドとして実装してあげて、それを FUSE.run に渡せば、あとはファイル操作をフックとして様々な処理をmrubyで書けるようになります。

インスタンスgetattr のタイミングで作成し、pathをキーにしてプールに保存しています。なので、同じpathを持ったファイル(インスタンス)への操作は、fusumaのプロセスがいる限りは保持されています。

今のところ、以下のフックを実装しています。カッコがRuby側で実装すべきメソッドとなります。今後の展開として、残りをひたすら地道に書く感じです...。

  • getattr(on_getattr)
  • readdir(on_readdir)
  • truncate(on_truncate(size))
  • open(on_open)
  • read(on_read/一気に全コンテンツを返せるon_read_allもある)
  • write(on_write(buf, offset))
  • release(on_release)

免責事項

本ツールとmruby gemはlibfuseの練習も兼ねて作っています。今のところ、特にマルチスレッド時の挙動など、細かいところの詰めがまだまだです。

こうしたらいいよ!という方針があれば是非プルリクエストをくださいませ...

ということで、なんとなく自作のファイルシステムが欲しくなった際などに、お試しいただければと思います。


ところで、明日のmruby Advent Calendarは 空席です 。是非、ご参加をお待ちしております...!!!1

seccompをmrubyで試す

seccomp とは、Linuxでプロセスのシステムコールの発行を制限する機能のこと。今回試すものはseccomp mode 2と呼ばれる、Linux3.5から搭載されたもので、システムコール単位での制限、特定の条件を満たす引数のみの許可/制限を実現できる。

バックエンドではlibpcapなどでも利用しているBPF(Berkeley Packet Filter)を利用していて、JITなどで高速に動作するらしい。

以下のサイトなども参照のこと。

mmi.hatenablog.com

yuzuhara.hatenablog.jp

今回、seccompを利用するためのCライブラリlibseccompを用いて、mruby-seccompを作成した。これを利用してシステムコールの発行制限を試す。

github.com

基本的な使い方

続きを読む

Haconiwa 近況(released 0.3.0)

Haconiwaの近況報告です。みなさんは、庭ですか?

まず、haconiwa-0.3.0 今すぐダウンロードは以下です:

次に何があったかを話します。

続きを読む

OCI Runtime Specification を読む

某Docker CTOの記事を見てから、ちゃんと見とかないとねという気持ちになり、まずはざっくり読んだ。

なにせ不勉強は承知、メモ程度ということで間違い等のご指摘をお待ちしています。

github.com

ソースは2016/08/02 14:30 時点のHEAD(95a6ecffd04732bdf7db0518fdfd59bcabdad442) です。

Introduction

Code of Conduct はまあ省略で。

Container Principles

  • Standard operations
    • ツールにより create, start, stop ができる
    • filesystemについて copy, snapshotができる
    • ネットワークツールで upload, downloadができる
  • Content-agnostic
    • PostgreSQL DB, PHP, Java, その他コンテナの中身に関係なく同じ操作ができる
  • Infrastructure-agnostic
    • OCI supported infrastructure ならどこでも動く
    • Virginiaのfiber hotel(っち何?)で作ったコンテナがプラクラで試されてパブクラで動く的な
  • Designed for automation
    • スタンダードがないと人力筋力コンテナ作成になってしまう的な
  • Industrial-grade delivery
    • エンプラから小規模なシステムまで、等しくINDUSTRIAL-GRADEなデリバリをする

Style and Conventions

  • これは本当にコーディング規約というか、"classID": 1048577 instead of "classID": "0x100001" とか、redundant prefixesをキープしてね(KILLじゃなくてCAP_KILLなど)という話が書かれていて、読むだけ
  • Goのコーディング規約っぽい内容も入ってるけどなんだろ

Roadmap

  • 1.0 でここまでやるぞーの話

Implementations

Project

  • プロジェクトの進め方?

Filesystem Bundle

  • a set of files organized in a certain way, and containing all the necessary data and metadata
  • OS X application bundles 的なファイルツリーの規約
    • config.json を含むこと。see below (Container Configuration file)
    • A directory representing the root filesystem of the containerrootfs/ みたいな規約があるといい。config.jsonで参照されていてほしい
  • これらのartifactsをtar archiveで固める

Runtime and Lifecycle

General Runtime and Lifecycle

  • こういうオペレーションができないといけないリスト。プラットフォームに関係ないもの
  • State
    • ociVersion, id, status(created/running/stopped), pid(as seen by the host), bundlePath, annotations(タグ)
  • Lifecycle
    • createがinvokeされる
    • config.json に従ってコンテナが作られる
    • additional actionsがあってもいい
    • startされたらuser-specified codeが走る
    • erroring out, exiting, crashing or the runtime's kill operationで止まる
    • delete でcreate stepで行われたことのundoが行われ、削除される
  • Errors
    • エラーの表示方針。 “generating an error MUST leave the state of the environment as if the operation were never attempted”
  • Operations
    • 言ってみればサブコマンド。こういうのにしてね、というのが書かれる。エラーの条件なども。
    • state, create, start, kill, delete
    • ん〜書いてある通りだな
  • Hooks
    • 設定見てねとのこと

Linux-specific Runtime and Lifecycle

  • File descriptors
    • “only the stdin, stdout and stderr file descriptors are kept open”
    • “additional file descriptors to the application to support features such as socket activation”だそうです。Socket Activationはsystemdのやつなど
    • これらfdは /dev/null にリダイレクトされててもいい
  • Dev symbolic links
    • /proc をマウントしたら、例えば /proc/self/fd -> /dev/fd のようなsymbolic linkを貼ってほしいとのこと

Configuration

General Configuration

  • config.jsonバインドしたGoのライブラリやJSON Schemaがすでにあるそう
  • Specification version
    • ociVersion the version of the OpenContainer specification
  • Root Configuration
    • path(e.g. "rootfs"), readonly
  • Mounts
    • マウントポイントたち。 destination,sourse,type,options
  • Process configuration
    • terminal(whether you want a terminal attached to that process) cwd env args(executable to launch and any flags as an array)
    • capabilities, rlimits, apparmorやselinuxの設定、noNewPrivileges
    • User - uid, gid, additionalGids(groups)
  • Hostname
    • hostnameです
  • Platform
    • イメージがターゲットとするプラットフォーム。linuxamd64です、など
  • Hooks
    • Prestart/Poststart/Poststop が定義できる
    • 「Runtime namespace」(コンテナを作った側のプロセスのネームスペース)で、hostのファイルシステムで実行される
    • 例を見ると、せやなという感じ
  • Annotations
    • タグです
  • で、全体の例として巨大なJSONが...

Linux-specific Configuration

  • この章は面白い
  • Default File Systems
    • /proc, /sys, /dev/pts, /dev/shm がほしい
  • Namespaces
    • type(例の pid, network, mount, ipc, uts, user, cgroup)と、もしnamespaceファイルを指定したければpath(/proc/1234/ns/pidだの、あるいは/var/run/netns/netfoo)を指定できる
  • User namespace mappings
  • Devices
    • コンテナで利用できるデバイスファイルたち
    • mknod的な表現でのtype(cとか), path, メジャーマイナー番号, fielMode, オウナーのuid/gid
    • /dev/null, /dev/zero, /dev/random など幾つかのデバイスファイルについては必ずランタイムが提供しないといけない
  • Control groups
    • そのままcgroup周り。コントローラーごとに記法が違って大変...
    • まあここも書いてある通り
  • Sysctl
    • あのsysctlだけど、“modified at runtime for the container”とのことでコンテナごとに変えられるもんでしたっけ...
  • seccomp
  • Rootfs Mount Propagation
    • こんなのも指定できるのね...
    • Shared Subtreeのやつ、 mount --make-private / とか
  • Masked Paths
    • コンテナから見えなくするパス。例のやつはマウント名前空間が共有でも /proc/kcore は見せない、ということ?
  • Readonly Paths
    • コンテナからリードオンリーにするパス
  • Mount Label
    • “mountLabel will set the Selinux context for the mounts in the container”

Solaris-specific Configuration

(省略...)

最後に用語集


次は image-spec を読む。