うなすけとあれこれ

2018年12月24日

OCI Image Format Specification v1.0.1を読んで

skopeo

まえおき

発端

めちゃくちゃ興味あり〼 https://t.co/gHfAV5rbEU

— うなすけ (@yu_suke1994) 2018年11月30日

頭の中には設計があるんですが、…
OCIの仕様を再確認するところから…

— Uchio KONDO 🔫 (@udzura) 2018年11月30日

とりあえずブログかキータ

— Uchio KONDO 🔫 (@udzura) 2018年11月30日

という訳で、書きました。

OCI Image とは

Open Container Initiativeによって定められた、コンテナイメージフォーマットの標準仕様です。

詳しくは Open Container Initiativeによるコンテナランタイムとコンテナイメージの最初の標準化作業が完了、「OCI v1.0」発表 - Publickey にて。

OCI Imageを触ってみる

現在、OCI ImageをdownloadできるDocker HubのようなWebサイトは知る限りありません。なので、自分でOCI Imageを作成する必要があります。

まずはDocker Imageから

まず、Docker Hubにて公開されているDocker imageをdownloadするところからです。以下のコマンドで、Docker Hubからダウンロードしたimageをtarballとして扱うことができるようになります。

$ docker image pull ruby:2.5.3-slim-stretch
$ docker save ruby:2.5.3-slim-stretch --output ruby_253_slim_stretch.tar

このtarを展開してみましょう。

$ tar -xf ruby_253_slim_stretch

すると、manifest.json というファイルができているので、見てみます。

[
  {
    "Config":"b1c1603e80c648f3ab902b0259ab846a7779d0780124bf9e417dd4b8c3cea296.json",
    "RepoTags":[
      "ruby:2.5.3-slim-stretch"
    ],
    "Layers":[
      "aeff88bcdbbd12ea45c023c45f97b870492092899651c811b2ef26ae7fdf3120/layer.tar",
      "c61a4dce9ddcebd63027d09811998052c9b2cdb3a379c297277cf755dfcf1420/layer.tar",
      "de2944e57fc93c2f354420cb36210fd1181687a990ffd7123600fdaecba3ee83/layer.tar",
      "49c3631e8651776127d66adb995e78af1e2cfc52b7a10a20df0d92d837258419/layer.tar",
      "eb50b8a8210f1b43ff1571598e66b694844b2dcf6fbaa0691e8af6b7c80dcaa7/layer.tar"
    ]
  }
]

なるほど、これはOCI image specを読むとわかるのですが、OCIに定められている形式のJSONではありませんね。

skopeo

ここで、containers/skopeo というtoolを使用して、Docker imageをOCI imageに変換してみます。

$ skopeo copy docker://ruby:2.5.3-slim-stretch oci:ruby-oci:latest

すると、 ruby-oci/index.json というファイルができているので、見てみます。

{
  "schemaVersion":2,
  "manifests":[
    {
      "mediaType":"application/vnd.oci.image.manifest.v1+json",
      "digest":"sha256:a3843587af4f3e838f3e1a10649631144d4dcf4391980b64f3b902d81048057c",
      "size":976,
      "annotations":{
        "org.opencontainers.image.ref.name":"latest"
      },
      "platform":{
        "architecture":"amd64",
        "os":"linux"
      }
    }
  ]
}

なるほど、これは OCI Image Specに定められている image manifest fileですね。

以下、OCI Image Format Specification v1.0.1

自分なりに理解しようと翻訳したもののメモになります。 正確性の保証はないです。誤訳とかあります。最後のほう力尽きてます。


Open Container Initiative Image Format Specification v1.0.1

Overviewで語られていること

high level image manifest にはcontentsとdependencies of the image including the content-addressable(連想?) identity of one of more file system layer changeset archives、展開すると最終的に実行可能なファイルシステムになる

image configuration にはapplication arguments, environmentsなどの情報

image indexには high level manifest list of manifests and discriptorsのpointが含まれる

それらのmanifestsは異なるimageの実装 ←プラットフォームや他の属性によって変化することができる

Build diagram

一度作成されたOCI imageは名前によって探索(discovered)、ダウンロード、hashによる検証、署名による信頼、OCI Runtime Bundleへの展開ができる

(No Title)

Understanding the specification

components of the specは以下を含む

optional featureとしてSignaturesやNamingが仕様に含まれるかもしれない。

OCI Image Media Types

HTTP responseのContent-Typeで上の値を返すなどのように、typeを返すなにかしらの方法を実装してもよい(MAY)、また実装はmedia typeとdigestを期待してよい? 実装は返却されたmedia typeを尊重する必要がある(SHOULD)

Compatibility Matrix

前方・後方互換を可能な限り維持する必要がある。

似た、または関連するmedia typeは以下

Relations

Image indexは複数のImage manifestを持つ。Image manifestとImage JSON(config)は1対1。Image manifestはLayerのtar archiveを複数持つ。 Discriptorは全ての参照を持つ。

OCI Content Discriptors

properties

Digests

こういう形式

sha256とsha512がRegistered algorithmsとされている。sha256はMUSTでsha512はMAY。

Example

{
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "size": 7682,
  "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
  "urls": ["https://example.com/example-manifest" ]
}

OCI Image Layout Specification

OCI Image LayoutはOCI content-addressable blobs と location-addressable references のための directory構造を表す(?) Layoutではtarやzipなどのarchive formats、nfsなどの共有ファイルシステム、http、ftp、rsyncなどのネットワークによるファイル取得を使用してもよい。

あるimage layoutと参照は、manifestと指定された順序で適用されるfilesystem layerとOCI runtime specificationのconfig.jsonへ変換できるimage configurationがあればOCI Runtime Specification bundleを何らかのtoolによって作成できる。(?)

Content

Example

$ cd example.com/app/
$ find . -type f
./index.json
./oci-layout
./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768

$ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51

Blobs

Example

$ cat ./blobs/sha256/9b97579de92b1c195b85bb42a11011378ee549b02d7fe9c17bf2a6b35d5cb079 | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 7143,
      "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      }
    },
...
$ cat ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 | jq
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 7023,
    "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 32654,
      "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
    },
...
$ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 | jq
{
  "architecture": "amd64",
  "author": "Alyssa P. Hacker <alyspdev@example.com>",
  "config": {
    "Hostname": "8dfe43d80430",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": null,
    "Image": "sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b",
...
$ cat ./blobs/sha256/9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0
[gzipped tar stream]

oci-layout file

これ。

{
    "imageLayoutVersion": "1.0.0"
}

index.json

必須。image-layoutの参照、descriptorsのentry pointになる。/index.json に置かれる。 "org.opencontainers.image.ref.name" にイメージのtagが格納される?

{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "size": 7143,
      "digest": "sha256:0228f90e926ba6b96e4f39cf294b2586d38fbb5a1e385c05cd1ee40ea54fe7fd",
      "annotations": {
        "org.opencontainers.image.ref.name": "stable-release"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 7143,
      "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      },
      "annotations": {
        "org.opencontainers.image.ref.name": "v1.0"
      }
    },
    {
      "mediaType": "application/xml",
      "size": 7143,
      "digest": "sha256:b3d63d132d21c3ff4c35a061adf23cf43da8ae054247e32faa95494d904a007e",
      "annotations": {
        "org.freedesktop.specifications.metainfo.version": "1.0",
        "org.freedesktop.specifications.metainfo.type": "AppStream"
      }
    }
  ],
  "annotations": {
    "com.example.index.revision": "r124356"
  }
}

OCI Image Manifest Specification

imageと、そのコンポーネントのために生成された一意なIDからハッシュ可能なimageのconfigurationimage modelをサポートした参照可能なimageを作成すること、platform固有のmanifestを含んだ"fat manifest"による複数architecture対応のimageの実現、OCI Runtime Specificationへの変換の3つを目標にしている。

Image Manifest

image indexはarchitectureやOSごとに展開可能なそれぞれのimageの情報を持つが、image manifestは特定のarchitecture、OSに対する単一のcontainer imageにおけるconfigurationとlayerの集合を提供する。

Image Manifest Property Descriptions

Example

{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 7023,
    "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 32654,
      "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 16724,
      "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 73109,
      "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
    }
  ],
    "annotations": {
    "com.example.key1": "value1",
    "com.example.key2": "value2"
  }
}

OCI Image Index Specification

Image Index Property Descriptions

Example

{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 7143,
      "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 7682,
      "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ],
  "annotations": {
    "com.example.key1": "value1",
    "com.example.key2": "value2"
  }
}

Image Layer Filesystem Changeset

change types

file types

File attributes

hardlinks

Platform-specific attributes

Createing

rootfs-c9d-v1/
    etc/
        my-app-config
    bin/
        my-app-binary
        my-app-tools

色々とfilesystemについての解説が続く

Non-Distributable Layers

OCI Image Configuration

用語

properties

Example

{
    "created": "2015-10-31T22:22:56.015925234Z",
    "author": "Alyssa P. Hacker <alyspdev@example.com>",
    "architecture": "amd64",
    "os": "linux",
    "config": {
        "User": "alice",
        "ExposedPorts": {
            "8080/tcp": {}
        },
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "FOO=oci_is_a",
            "BAR=well_written_spec"
        ],
        "Entrypoint": [
            "/bin/my-app-binary"
        ],
        "Cmd": [
            "--foreground",
            "--config",
            "/etc/my-app.d/default.cfg"
        ],
        "Volumes": {
            "/var/job-result-data": {},
            "/var/log/my-app-logs": {}
        },
        "WorkingDir": "/home/alice",
        "Labels": {
            "com.example.project.git.url": "https://example.com/project.git",
            "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
        }
    },
    "rootfs": {
      "diff_ids": [
        "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
        "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
      ],
      "type": "layers"
    },
    "history": [
      {
        "created": "2015-10-31T22:22:54.690851953Z",
        "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
      },
      {
        "created": "2015-10-31T22:22:55.613815829Z",
        "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
        "empty_layer": true
      }
    ]
}

Annotations

Rules

Pre-Defined Annotation Keys

Back-compatibility with Label Schema

org.opencontainers.image org.label-schema Compatibility notes
created build-date Compatible
url url Compatible
source vcs-url Compatible
version version Compatible
revision vcs-ref Compatible
vendor vendor Compatible
title name Compatible
description description Compatible
documentation usage URLの場合にはCompatible
authors Label Schemaにはない要素
licenses Label Schemaにはない要素
ref.name Label Schemaにはない要素
schema-version OCI Image Specにはない要素
docker.*, rkt.* OCI Image Specにはない要素

Conversion to OCI Runtime Configuration

Verbatim Fields

annotation fields

Parsed Fields

Optional Fields

Annotations

Considerations

Extensibility

Canonicalization

JSON

Extended Backus-Naur Form

Tweet
2018年12月24日
2018年11月30日

Railsのテストを実行する環境をdockerで構築したが使いみちがない

docker-compose ps ※ Rails アプリのテストではなく、Rails本体のテストについての話です。

僕がそうだったように、皆さんにもふと「Rails本体のテストを実行してみたいな〜」と思うことがあるでしょう。

ところが、Rails本体のテストというのは大変です。ActiveRecordひとつ取ってみても、網羅するためにはMySQL、PostgreSQL、MariaDB、SQLiteの4つのRDBを用意しなければいけません。

もちろん、公式でそのような環境を構築するためのものは用意されています。

https://github.com/rails/rails-dev-box

rails-dev-boxを使うと、Vagrantを用いて、Rails本体の開発環境を構築できます。(yahondaさんありがとうございます)

ですが、Dockerが仮想環境の主流となっている今、VagrantでなくDockerを使いたい、そう思うのもおかしくはないでしょう。ないですよね?

なので、docker-compose を使用して、Railsのテストを実行する環境を構築してみました。以下、それに伴って行なったことの解説になります。

Dockerfile

FROM ruby:2.5.3-stretch

WORKDIR /rails
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \
  && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && apt update && apt install --assume-yes \
    ffmpeg \
    sqlite3 \
    imagemagick \
    mupdf \
    mupdf-tools \
    poppler-utils \
    libmariadbclient-dev \
    libsqlite3-dev \
    postgresql-contrib \
    libpq-dev \
    libxml2 \
    libxml2-dev \
    libxslt1-dev \
    libncurses5-dev \
    mysql-client \
    git \
    make \
    nodejs \
    yarn \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

COPY . .
RUN bundle install --jobs=4
RUN npm install
RUN cd actionview && npm install

FROM から WORKDIR の指定までは飛ばして、aptによるパッケージのインストールを見てみます。 ここで指定しているパッケージ郡ですが、 https://github.com/rails/rails-dev-box/blob/master/bootstrap.sh を参考にしました。

続く COPY . . で、 rails/rails のファイルを一気にimageに追加しています。いわゆるDocker image buildのお作法としては、Gemfile、Gemfile.lock 、package.json などのファイルをCOPYして、bundle installなりnpm installなりを行ってからアプリケーションのコードをCOPYしてくるのが一般的です。しかしRailsのしかも開発環境では、Rails gem自体がActiveRecordやActiveModelなどの複数のgemに依存しており、それらがmonorepo構成で rails/rails に含まれているため、このようにしないと bundle installが実行できません。

あとは bundle installとnpm installを実行しているだけです。

docker-compose.yml

version: '3'
services:
  rails:
    build: .
    command: /bin/bash
    environment:
      - MYSQL_HOST=mariadb
      - PGHOST=postgres
      - PGUSER=postgres
      - REDIS_URL=redis://redis:6379
      - MEMCACHE_SERVERS=memcached
    tty: true
    volumes:
      - ".:/rails"
    links:
      - mysql
      # - mariadb
      - postgres
      - redis
      - memcached
      - rabbitmq
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
  postgres:
    image: postgres:11.1-alpine
  mariadb:
    image: mariadb:10.4.0-bionic
    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
  redis:
    image: redis:5.0.1-alpine
  memcached:
    image: memcached:1.5.12-alpine
  rabbitmq:
    image: rabbitmq:3.7.8-alpine

Rails本体のテストを行うにあたり、必要な関連サービスはMySQL PostgreSQL MariaDB Redis Memcached RabbitMQ の6つになります。SQLiteについてはrails service内に含まれています。

testの実行

`$ docker-compose up -d rails でrails serviceが立ち上がります。そしたら、

$ docker-compose exec rails bash

でcontainerに入り、testを実行します。

とはいえ、test suiteはTravis CIで実行されることに最適化されているため、以下のようなpatchを当てる必要がありました。

testそのものは、travis.ymlを参考に、このように実行します。

$ GEM=am,amo,as,av,aj,ast ci/travis.rb

このコマンドでは、ActionMailer、ActiveModel、ActiveSupport、ActionView、ActiveJob、ActiveStorageのtestを実行します。

例えばActiveRecordの、SQLite3でのテストの実行結果は以下のようになりました。

root:/rails# time GEM=ar:sqlite3 ci/travis.rb

[Travis CI] activerecord with sqlite3
Running command: bundle exec rake sqlite3:test

...snip...

Rails build finished successfully

real    63m17.484s
user    12m22.931s
sys     6m50.865s
root@ebdba3a4da12:/rails#

あきらめたやつ

multi stage buildによる Node.js ランタイムの構築

tootsuite/mastodon などで採用されているテクニックですが、multi stage buildを用いてNode.js、npm、yarnのバイナリを main imageに追加することにより、パッケージマネージャやバイナリの直接ダウンロードと比較して高速に別言語のランタイムを構築することができます。今回もmsatodonと同様にNode.js、npm、yarn を COPY --from node で持ってこようとしたのですが、 cannot find module'../lib/utils/unsupported.js' というエラーが発生し、解決策として出てくるのがNode.jsのサイインストールばかりだったので、aptからインストールすることにしました

で、これどうするのか

どうすればいいんでしょうね。

他の皆さんはどうしているのか

気になったのでTwitterで聞いてみたところ、Travis, VPS, localなどがあるようですね。確かにlocalhostに直にインストールしてしまうのが一番楽だと思いました。

皆さんRailsのテストってどうやってますかね。あ、RailsアプリのテストではなくRails本体のテストのことです。

— うなすけ (@yu_suke1994) 2018年11月21日

VPS借りてUbuntu 18.04入れて回してます。期待する答えじゃないかもしれませんが、何か困ってますかね。

— Yasuo Honda (@yahonda) 2018年11月21日

homebrewある環境だったらMySQLとPostgreSQL bottleで入れてActiveRecordのtestに必要なdatabaseやuser作るのは1分ぐらいあればできるので僕はローカルにインストールしてますね。

— Ryuta Kamizono (@kamipo) 2018年11月21日

すごい手抜きなやり方だとRailsをforkした自分のリポジトリに対してtravis設定して雑にpushしながらテストするとかですかね?

— sue445 (@sue445) 2018年11月21日
Tweet
2018年11月30日
2018年10月11日

Itamaeのメンテナになりました

invited

きっかけ

@ryot_a_rai こんどitamaeの話をしたいので飲みに行きましょう!!!! CC @sue445

— うなすけ (@yu_suke1994) 2018年10月7日

僕が以前書いた、落ちていたitamaeのintegration specを直しました から、@sue445 さんもtravis-ciに移行させるpull reqを出したりと、停滞していた開発が再開しそうな流れになっていました。

しかし、作者の @ryotarai さんに時間がないのか、なかなかmergeまで持っていくことができていませんでした。 そこで冒頭のtweetがあり、3人で集まった結果、 僕とsue445さんが itamae-kitchen organization にMemberとして追加されることになりました。

【速報】.@yu_suke1994 さんが .@ryot_a_rai さんを詰めた結果(違)、Itamaeのコミッタになりました! pic.twitter.com/gSo2oTrhz1

— sue445@10/8技術書典5 か75 (@sue445) 2018年10月10日

(今気づいたのですが、現時点で僕のcommitはItamaeには入っていないんですね)

Itamae について思っていること

近年のWebアプリケーションインフラ界隈は、Docker、Kubernetesなどによって Cloud Native になっていく流れが主流となっているように感じています。今所属している会社でも、Rails on Docker on Kubernetes with GKE という構成になっており、いわゆる「モダン」と呼ばれる構成からはインスタンスのプロビジョニングという概念が消えつつあります。

Itamaeに限らず、ChefやAnsibleなど書くことになる機会も減っていくでしょう。

ただ、世間の潮流がどれだけ Cloud Native になったとしても、インスタンスの構築という作業が消えてなくなることは絶対にありません。それに、個人レベルで生のマシンの構築を行なったりするという概念もなくなりはしないでしょう。

そのような場合において、Itamaeを選択肢のひとつとして十分に検討対象であるという状況を続けていきたいと考えています。何故なら僕はRubyistですし、構成管理ツールの中ではItamaeが最も好きだからです。

今後のItamaeにおいて、後方互換を失う規模の変更が入るということはないと思いますが、ryotaraiさん、sue445さんと話していてまだまだ改善する部分はあるという認識は共通しています。細かい改善をしながら粛々とメンテナンスが行なわれていくことになるでしょう。

今後よろしくお願いします。

Tweet
2018年10月11日
2018年10月08日

第51回 情報科学若手の会に参加しました

もらった寿司

幹事のどくぴーや、前年参加のなっちゃん、puhitakuにオススメされたので情報科学若手の会に参加することにしました。

発表の内容は想像以上にアカデミックであり濃く深いものばかりで、正直なところ着いていくのがやっと、いや着いていけないというのが現実でした。

ただ、それは参加を後悔しているという訳ではなく、自分の知らなかった分野、知っていたつもりでもそれは表層だけでまだまだ深淵がある分野についてその存在を知ることができたということで、参加できたことはとても価値があることだと感じました。

僕は初参加だったのですが、ショート発表を1つ、LTを1つ行ないました。初対面の人が多いイベントでは、発表する側に回ったほうが認知が上がること、参加枠も発表者としてだと倍率が低くなることなどの利点があるためです。(定員問題はなさそうでしたが)

今回のショート発表については外部公開はしませんが、LTについては過去の表参道.rbでのmail gemの話をしました。参加者にRubyistが少ないので知らない人が大半だと考えたためです。発表自体はそれなりに盛り上がったようで良かったです。

懇親会でのカスタムキャスト将棋(2人で交互にカスタムキャストのパラメータをいじって好みのアバターを作成する競技)はとても楽しかったです。オタクが終わっていく様子は面白いですね。僕は終わりました。

僕と @ntddk さんで作った限界美少女です #wakate2018 pic.twitter.com/9vlkenzVQo

— うなすけ (@yu_suke1994) 2018年10月7日

アッアッアーーーーー #wakate2018 pic.twitter.com/kXHZ2L7nnT

— うなすけ (@yu_suke1994) 2018年10月7日

来年も参加したいです。もっというなら、他のアカデミックな発表に負けないような濃い内容の発表を持っていきたいと思いますが、さてそんなことができるかな……

Tweet
2018年10月08日
2018年09月30日

高専DJ部 #20 でした

kosendj-bu #20

セトリ

  1. 夏祭り - ジッタリン・ジン
  2. Champagne Showers - LMFAO Feat. Natalia Kills
  3. レーザービーム (Album-mix) - Perfume
  4. One More Time (Chris Moody Remix) - Richard Grey
  5. Atlantis (Extended Mix) - Deniz Koyu
  6. Kill EVERYBODY - Skrillex
  7. Mind Mapping - Ryu☆
  8. In The Place (Original Mix) - FRANO
  9. A Lot Like You - Zac Waters
  10. 君をのせて(totsumal ゼンニチリスペクト Remix) - totsumal
  11. Gekka (Original Mix) - Nhato
  12. Black Mirror (Extended Mix) - James Dymond
  13. Step Back (Original Mix) - Afrojack, MC Ambush
  14. Epidemic (Extended Mix) - SaberZ
  15. Children (Extended Mix) - Vigel
  16. Never Say Never (Extended Mix) - SICK INDIVIDUALS

夏も終わりですね。

Tweet
2018年09月30日
2018年09月17日

大江戸Ruby会議07で登壇しました

雷5656会館

発表資料

oedo07 - unasuke - Rabbit Slide Show

埋め込みがあまりうまくいかないので上のリンクからお願いします。

oedo07

ブログネタになるはずだった

スライドの中でも参照したのですが、もともとこれはこのブログの1記事として出す予定のものでした。

ただ、tqrk12のときに大江戸Ruby会議の登壇者を募集していると聞き、じゃあこっちに回してしまえということにしました。

Rabbitにはpull reqを出せる、と言ったものの

今の僕に理解できるのは、表示が絡まない部分のみなんですよね。GTKで書かれているところに関しては全くわかりません。また、slide.rabbit-shocker.orgも、Rails、Sinatraくらいしか触ったことのない僕にとっては、サッと読んだ程度では理解に相当時間がかかりそうでした。

とはいえ改善点は見えているのでそのうちパッチを送りたいと思います。

OSS信者なのか

PowerPointやKeynoteを用いた、デザインの優れたスライドをRabbitで作成するのは相当大変です。ではなぜ僕がそうするのかというと、発表でも話しましたが、過去の自分の資料が見られなくなってしまったからです。

PDF等に書き出していなかった自分が悪いといえばそれまでなのですが、プロプライエタリではこのようなことも起こりえます。

Tweet
2018年09月17日
2018年08月31日

Mastodon おひとりさまインスタンスを立てました

はい

立てました。

理由

主にMastodonでは末代(@unasuke@mstdn.maud.io)に軸足を置いて色々やっていました。しかし自分自身がRailsアプリケーションの運用を業務で担当しており得意とすることから、やはり自分のドメイン下で動かしてこそだろうと思いsingle user instanceを立てることにしました。(以前にも非公開で運用していたインスタンスはあったのですが、解消できない不具合があり閉じてしまっていました)

Kubernetesの勉強と書いたように、GKEの上で運用しています。費用を抑えるために全てのPodをPreemptible VM instance(最大でも24時間までしか稼動しない)上に配置しています。これはちょっとまずくて、Redisも24時間で自動的に再起動になるのでキャッシュが揮発してしまいます。この辺をなんとかしないといけないなーと思っているので、まだ積極的にリモートフォローはできない状態にいます。

また、このインスタンスでのアカウント作成を開放するつもりはありません。そもそもこのドメイン以下にアカウント作りたいか?という問題、特に今の構成では安定性が保証できないという問題などがあるためです。 5人以上が月に数百円の運営費用を負担してでも、というのであれば検討はしますが、基本的に開放は無いです。

参考にしたweb page

Tweet
2018年08月31日
2018年07月23日

寝転がったままパソコンを使う

外観

ごろ寝デスク

スライドつくりながら、これになることばかり考えている

第133回 夏休み特別企画・夢の仰向けUbuntu生活:Ubuntu Weekly Recipe|https://t.co/JqWu8qNOVX … 技術評論社 https://t.co/mWT75PMzGv

— うなすけ (@yu_suke1994) 2018年7月8日

僕が今使っている椅子は、上京したときに買った安いもので、ずっと座っていると腰が痛くなってくるし、角度によっては酷く軋むので、あまり集中して作業のできるものではありませんでした。

そんな訳でもっぱらベッドの上で作業していたのですが、胡座も長い時間は続けられませんし、寝転がるのも首を上げなくてはならず、どちらにせよ長時間の集中が厳しい状況にありました。(ノートPCです)

もちろん良い椅子を買うことも検討しましたが、とりあえず安価に始められそうということで、前述の記事にあるような寝転がったまま作業ができる環境を整えることにしました。

材料・費用

以下の物品を購入しました。

品名 価格 参照先
メタルラック棚板 幅130cmタイプ MR-1346T * 2 ¥9,892 https://www.irisplaza.co.jp/index.php?KB=SHOSAI&SID=K546794
【4本セット】メタルラックポール 120cmタイプMR-12P ¥2,894 https://www.irisplaza.co.jp/index.php?KB=SHOSAI&SID=K540357F
メタルラック用 大型キャスター MR-475C 4個入り ¥2,138 https://www.irisplaza.co.jp/index.php?KB=SHOSAI&SID=K539420
ナベ頭小ねじ(鉄/ユニクローム) M4×50 1パック(70個) ¥496 https://ihc.monotaro.com/item/C05503601/
Dell 23.8インチワイドモニタ P2418D ¥33,458 https://www.dell.com/ja-jp/shop/accessories/apd/210-amxb?c=jp&l=ja&s=dhs&cs=jpdhs1&sku=210-AMXB
ThinkPad Bluetooth ワイヤレス・トラックポイント・キーボード - 英語 ¥10,962 https://www.lenovo.com/jp/ja/accessories-and-monitors/keyboards-and-mice/keyboards/KEYBOARD-US-English/p/0B47189

合計で¥5,9840 でした。上の価格には配送料が含まれたりそうでなかったりしますが、良い椅子(ここではバロンチェアとする)より安価に済みました。

これはメタルラックにディスプレイを固定している部分です。

VESA

外観

すごく廃人っぽいです。

外観

感想

利点

欠点

今後の課題

総合的には快適です。皆さんもどうですか。

Tweet
2018年07月23日
2018年07月16日

#railsdm と #kosenconf と #kosendj だった3連休

kosenconf in tokyo 2018

イベント3連続

2018年の7月14日はRails Developers Meetup 2018 Day 3 Extreme、 15日は高専カンファレンス in 東京 2018、 16日は高専DJ部 #19、 という非常に詰め込まれた3連休でした。

それぞれでブログを書くほどの気力がもうないので、この1本にまとめて書きます。

Rails Developers Meetup 2018 Day 3 Extreme

発表資料です。

Railsdm 2018 day 3 extreme - unasuke - Rabbit Slide Show

「Railsのissueを毎日読む方法」という題で登壇させて頂きました。

実際のところ、pixivFANBOXでのwatchは結構継続できており、ありがたいことに支援者も6名ほどおられます。

うなすけ[pixivFANBOX]

スライド内のコードの一片もなく、技術というよりは心構えに関する発表でした。その割には良いと言ってくださる方がいらっしゃったのはとても嬉しかったです。

うなすけ pic.twitter.com/hL0Rw6I4QA

— ゆーけー / Yuki AKAMATSU (@ukstudio) 2018年7月14日

#railsdm うなすけさんのAMAへのセルフ質問、最高によかったですね

— tatsuosakurai 🍺🤖 (@tatsuoSakurai) 2018年7月16日

ちなみに、Rubyはwatchしていません。mruby/mrubyはwatchしています。理由はRubyの開発の主戦場がGitHubではないからです。

高専カンファレンス in 東京 2018

発表資料です。

kosenconf in tokyo 2018 - unasuke - Rabbit Slide Show

高専カンファコミュニティと僕の関わりについて、もしくは他のコミュニティとの関わりについて僕の心構えとこれまでについて発表させて頂きました。 また、当日スタッフのような感じで、第1会場の録画を担当しました。

railsdmでもそうですが、最近はよく僕の顔写真がインターネットに出回っているので、現実空間でお会いした方に認知されていただいているので有難いです。

うなすけはこんないい話しないよ、これはうなすけのニセモノだよ

— ごさいようじょ (@tochikuji) 2018年7月15日

AMAブース開きました! #kosenconf pic.twitter.com/N66MXOizfF

— あそなす (@asonas) 2018年7月15日

うなすけさん、実在していた! #kosenconf

— 湊川あい🌱マンガでわかるUnity Web連載中 (@llminatoll) 2018年7月15日

高専DJ部 #19

これはもともとasonasさんが運営の主体だったのですが、高専カンファ in 東京 2018の運営で忙しいということもあり、前回くらいから運営のお手伝いをしています。 あと、今回はDJはしていません。

前日のカンファでも、イベントのさ中でも新規入部希望者がいらっしゃったのはとても嬉しいです。

#kosendj pic.twitter.com/8tty1mueDp

— dachiba (@dachiba) 2018年7月16日

3日間の感想

ギュッとなっていて、楽しかったですが、体力の衰えも感じました。今後もやっていきましょう。

Tweet
2018年07月16日
2018年06月11日

The world of mail.gem (maybe) not you know

I am contributor of the mail.gem

RubyKaigi 2018のLTにCFPを提出しましたが、残念ながらnot acceptedとなってしまいました。

なので、その内容を先日開催された表参道.rbで発表してきたのですが、LTにする過程で端折った様々を補完するために記事にまとめました。

発表資料自体はここにあります。

https://github.com/unasuke/omotesandorb-35

mail.gemとは

mail.gemはRubyでemailを扱うためのgemであり、actionmailerの依存関係にも含まれるように、世界中で使用されているgemです。

mail | RubyGems.org | your community gem host

RailsのCI

rails/railsのCIでは、以下に示すように警告が有効になっています。

https://github.com/rails/rails/blob/fcfe29cd2641b2ce3c01bc13f39d617ec302fc8d/actionmailer/Rakefile#L11-L17

# Run the unit tests
Rake::TestTask.new { |t|
  t.libs << "test"
  t.pattern = "test/**/*_test.rb"
  t.warning = true
  t.verbose = true
  t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
}

さて、ruby-head、つまりRuby 2.6以降では、以下のようなcase-whenに対して警告が出るようになっていました。(余談を参照)

case cond
  when 1
    do_something
  when 2
    do_something_another
end

そして、Railsはruby-headでもCIを実行しています。そのなかで依存しているgemのコードに、whenが1段深いインデントをしているものが含まれていたので、CIで大量のwarning messageが出るようになってしまいました。

https://travis-ci.org/rails/rails/jobs/365773392

依存しているgemのうち、簡単に直せるものについては次のようなpull reqによって修正されています。

Address `warning: mismatched indentations at ‘when’ with ‘case’` by yahonda · Pull Request #74 · rails/rails-dom-testing

しかしmail.gemについては、そのgem単体で発生しているwarningが多く、修正の手間が大きいのではないかという懸念がありました。

そのようなことを @koicさんや@yahondaさんが #asakusarb にて話されており、偶然その近くにいた僕が興味を持ってやってみようということになりました。

よくわからない、自動生成されたコード

まずは、警告が出ているコードを見てみます。中には確かにインデントの揃っていないcase-whenがありましたが、それよりも僕は次のコードに疑問を抱きました。

https://github.com/mikel/mail/blob/fbc5d91ae9b68b3c4ad450a22055a74dfce1caf9/lib/mail/parsers/addresslistsparser.rb#L33166-L33173

    when 36 then
        begin
 local_dot_atom_pre_comment_e = p-1         end
        begin
 local_dot_atom_e = p-1         end
        begin
 address.local = '"' + qstr + '"' if address        end
        begin

Ruby では、次のような後置ifがある場合に、その条件が偽であれば前置されている式は実行されないという文法があります。

puts 'message' if false  # この場合 'message' は出力されない

このときにifにendが続いてblockになっている場合、後置ifのような動きをするのか、それとも前置の式が評価されてからif blockの中に入るのか僕は即座にはわかりませんでした。

そこでその場にいらしていた @amatsuda さんに伺ってみたところ、そもそもそのコードは何らかのツールにより生成されたもののように見える、というアドバイスを頂きました。

自動生成されたコードであるならば、その成果物に対してあれこれ修正するのは再度生成した場合に全て上書きされるので、無意味となります。生成元に対して何らかの対処をしなければなりません。

Ragel

それでは、mail.gemのRubyコードは一体何によって生成されているのでしょうか。

コードの生成といえば、何らかのスクリプト、あるいはタスクランナーによって生成されることが多いでしょう。例えば一般的にはMakeがその役目を担うでしょうし、RubyのプロジェクトであればRakeも使われます。

というわけでRakefileの中を見てみると、どうやら ragel というコマンドを呼び出して、 .rl から .rb を生成しているようです。 https://github.com/mikel/mail/blob/fbc5d91ae9b68b3c4ad450a22055a74dfce1caf9/tasks/ragel.rake#L12-L21

  # Ruby parsers depend on Ragel parser definitions
  # (remove -L to include line numbers for debugging)
  rule %r|_parser\.rb\z| => '.rl' do |t|
    sh "ragel -s -R -L -F1 -o #{t.name} #{t.source}"
  end

  # Dot files for Ragel parsers
  rule %r|_parser\.dot\z| => '.rl' do |t|
    sh "ragel -s -V -o #{t.name} #{t.source}"
  end

ragelというキーワードでGoogle検索してみると、次のようなるびまの記事が見付かりました。

Ragel 入門: 簡単な使い方から JSON パーサまで

記事によると、Ragelはステートマシンコンパイラのようです。emailのデータをパースするのに使われていそうだ、というところまでわかりました。公式サイトは以下です。

http://www.colm.net/open-source/ragel/

ひとまずRagelをcloneして、内部を眺めてみることにします。

Ragelをどうにかする

公式にもあるとおり、以下のようにしてRagelをcloneしました。

$ git clone git://colm.net/ragel.git

さてここからどうすればいいのかがわかりませんでした。READMEはありますが非常に簡素なもので、コンパイル方法がわかりません。僕は普段はRubyで開発しているので、C言語で記述されているプロダクトのビルドの作法に疎いのです。

しかしREADMEには Colm is a mandatory dependency. という記述があり、とりあえずそれが必要であることはわかりました。

なんもわからんhttps://t.co/8o5oaN7zfZ

— うなすけ (@yu_suke1994) 2018年4月12日

Colm

先程RagelをcloneしたURLにも含まれるように、Colmが同じドメイン下で公開されていました。

http://www.colm.net/open-source/colm/

公式サイトの記述に

Colm is a programming language designed for the analysis and transformation of computer languages.

とあるように、これはプログラミング言語のひとつのようです。

mail.gemを直すのに、新しくプログラミング言語を習得し、初めて使うツールの学習もしなけれならないとなると相当時間がかかる上に難易度も高いので、別の方法が無いか考えることにしました。

要はgofmtがあればよい

自動生成されたコードのスタイルがめちゃめちゃであれば、それを自動整形してくれる、gofmtのようなものがあれば解決します。

そのようなRubyの自動整形ツールとしては、有名なものであればRuboCopが挙げられるでしょう。RuboCopは -a をオプションとして渡すことで、対応しているCopについては自動で整形してくれます。

しかしRuboCopはあくまでも静的解析ツールであり、自動整形ツールではありません。自動整形の機能についても、誤動作が無いというわけではありません。

ruby-formatter/rufo

用途に合致するものがないか調べていたところ、以下のgemが見付かりました。

https://github.com/ruby-formatter/rufo

rufoはRipperという、Rubyに同梱されているRubyの構文解析器を使用してコードの整形を行ないます。なのでその整形結果に関してはある程度の信頼性があると判断してよいと考え、これを使って整形することにしました。

動かないRakefile

rufoによる自動整形を、Ragelによるコード生成の後に実行すればよいので、そのようにRakefileを書き変えると、エラーによりコード生成ができませんでした。そこでrufoを呼び出している部分を消し、変更が無い状態でもういちどrakeを実行してみましたが、同様にエラーで生成ができません。

ある時点からRakeの挙動が変わってしまい、既存のRakefileのままではコード生成ができなくなってしまっているようです。Ragelによるコード生成はそこまで頻繁に実行されるものではない(前回は2017年11月)ので、mail.gemのメンテナはこの問題に直面してないようです。

mail.gemのgemspecに記述されているrakeと、その時点でローカルで使用されているrakeの間に入ったコードのどこから挙動が変わったのかを調べる必要があります。そのきっかけとなるcommitを見付けられれば、対処法もわかるはずです。

調査したところ、v12.0.0 では成功し、 v12.1.0 では失敗することがわかりました。

https://github.com/ruby/rake/compare/v12.0.0…v12.1.0

さらに調査を進め、次のpull reqがmergeされたことにより、挙動が変わってしまったことが判明しました。

Chained extensions by pjump · Pull Request #39 · ruby/rake

これによって対象となるrlのファイル名の解決に失敗するようになったので、以下のpathmapのドキュメントを参考にして、次のpull reqを作成しました。

Set full path of the ragel source file to rake task by unasuke · Pull Request #1221 · mikel/mail

また、結果としてrake taskが正常に実行できるようになったので、前述のpull reqに依存する形で以下のpull reqを作成しました。

Reduce warnings “mismatched indentations” on ruby 2.6 by unasuke · Pull Request #1222 · mikel/mail

merge

なんとありがたいことに、それほど時間を置かずどちらのpull reqもmergeしてもらうことができました。よかったですね。

やった!!!!!! #asakusarbhttps://t.co/5tHqN7VWuS

— うなすけ (@yu_suke1994) 2018年4月13日

余談

case-whenでインデントが以下のようになっていないと警告が出る件についてですが、おそらく以下のcommitで有効になったようです。

ところがそれに対し、次のような報告が上げられています。

Bug #14674: New mismatched indentations warnings? - Ruby trunk - Ruby Issue Tracking System

I strongly believe that it is not Ruby’s parser job to warn us about styling, especially if there’s no strong reason to suspect that it’s a programmer error.

case-whenで1段深くインデントするのはよくあることだし、そのようなstyleのcheckはRubyのperserのやることではない、という反対意見ですね。

その報告によって次のようなcommitが為されており、結局のところcase-whenではwhenが1段深くインデントされていても警告は出ないようになっています。

$ ruby -v
ruby 2.6.0dev (2018-06-10 trunk 63625) [x86_64-linux]

$ cat test.rb
case true
  when true
    p 'true'
  when false
    p 'false'
end

$ ruby -w test.rb
"true"

よかったですね。

Tweet
2018年06月11日
古い投稿