めちゃくちゃ興味あり〼 https://t.co/gHfAV5rbEU
— うなすけ (@yu_suke1994) 2018年11月30日
頭の中には設計があるんですが、…
— Uchio KONDO 🔫 (@udzura) 2018年11月30日
OCIの仕様を再確認するところから…
とりあえずブログかキータ
— Uchio KONDO 🔫 (@udzura) 2018年11月30日
という訳で、書きました。
Open Container Initiativeによって定められた、コンテナイメージフォーマットの標準仕様です。
詳しくは Open Container Initiativeによるコンテナランタイムとコンテナイメージの最初の標準化作業が完了、「OCI v1.0」発表 - Publickey にて。
現在、OCI ImageをdownloadできるDocker HubのようなWebサイトは知る限りありません。なので、自分でOCI 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ではありませんね。
ここで、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ですね。
自分なりに理解しようと翻訳したもののメモになります。 正確性の保証はないです。誤訳とかあります。最後のほう力尽きてます。
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の実装 ←プラットフォームや他の属性によって変化することができる
一度作成されたOCI imageは名前によって探索(discovered)、ダウンロード、hashによる検証、署名による信頼、OCI Runtime Bundleへの展開ができる
components of the specは以下を含む
optional featureとしてSignaturesやNamingが仕様に含まれるかもしれない。
application/vnd.oci.descriptor.v1+json
application/vnd.oci.layout.header.v1+json
application/vnd.oci.image.index.v1+json
application/vnd.oci.image.manifest.v1+json
application/vnd.oci.image.config.v1+json
application/vnd.oci.image.layer.v1.tar
application/vnd.oci.image.layer.v1.tar+gzip
application/vnd.oci.image.layer.nondistributable.v1.tar
application/vnd.oci.image.layer.nondistributable.v1.tar+gzip
HTTP responseのContent-Typeで上の値を返すなどのように、typeを返すなにかしらの方法を実装してもよい(MAY)、また実装はmedia typeとdigestを期待してよい? 実装は返却されたmedia typeを尊重する必要がある(SHOULD)
前方・後方互換を可能な限り維持する必要がある。
似た、または関連するmedia typeは以下
application/vnd.oci.image.index.v1+json
application/vnd.docker.distribution.manifest.list.v2+json
(mediaTypeが違う?)application/vnd.oci.image.manifest.v1+json
application/vnd.docker.distribution.manifest.v2+json
application/vnd.oci.image.layer.v1.tar+gzip
application/vnd.docker.image.rootfs.diff.tar.gzip
互換性がある(入れ替え可能)application/vnd.oci.image.config.v1+json
application/vnd.docker.container.image.v1+json
Image indexは複数のImage manifestを持つ。Image manifestとImage JSON(config)は1対1。Image manifestはLayerのtar archiveを複数持つ。 Discriptorは全ての参照を持つ。
mediaType
digest
size
urls
annotations
data
こういう形式
sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b
sha512:401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b372742...
sha256とsha512がRegistered algorithms
とされている。sha256はMUSTでsha512はMAY。
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"urls": ["https://example.com/example-manifest" ]
}
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によって作成できる。(?)
blobs
directory
oci-layout
file
imageLayoutVersion
というfieldを持つ必要がある。index.json
$ 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
のサブディレクトリ以下にあるディレクトリは、各ハッシュアルゴリズムごとにまとめられている。そのディレクトリ以下に、実際のファイルが格納されている。
blobs/<alg>/<encoded>
<alg>:<encoded>
と対応している$ 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]
これ。
{
"imageLayoutVersion": "1.0.0"
}
必須。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"
}
}
imageと、そのコンポーネントのために生成された一意なIDからハッシュ可能なimageのconfigurationimage modelをサポートした参照可能なimageを作成すること、platform固有のmanifestを含んだ"fat manifest"による複数architecture対応のimageの実現、OCI Runtime Specificationへの変換の3つを目標にしている。
image indexはarchitectureやOSごとに展開可能なそれぞれのimageの情報を持つが、image manifestは特定のarchitecture、OSに対する単一のcontainer imageにおけるconfigurationとlayerの集合を提供する。
schemaVersion
mediaType
config
layers
annotations
{
"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"
}
}
application/vnd.oci.image.manifest.v1+json
← これ{
"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"
}
}
application/vnd.oci.image.layer.v1.tar
はtar archiveでなくてはならず、またtar archiveの結果となるpathに重複したエントリを含んではいけないrootfs-c9d-v1/
etc/
my-app-config
bin/
my-app-binary
my-app-tools
色々とfilesystemについての解説が続く
application/vnd.oci.image.layer.nondistributable.v1.tar
というmediaTypeでないといけないapplication/vnd.oci.image.config.v1+json
sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9
ChainID(L₀) = DiffID(L₀)
ChainID(L₀|...|Lₙ₋₁|Lₙ) = Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ))
VARNAME=VARVALUE
という形式で格納される。
layers
である必要がある。実装は不明な値が入っていた場合はエラーを出す必要がある。{
"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
}
]
}
com.example.myKey
みたいなorg.opencontainers
はOCIによって予約済みなので使用してはならない。org.opencontainers.image
はOCIによって予約済みなので使用してはならない。(他のOCI仕様についても同様)org.opencontainers.image.created
org.opencontainers.image.authors
org.opencontainers.image.url
org.opencontainers.image.documentation
org.opencontainers.image.source
org.opencontainers.image.version
org.opencontainers.image.revision
org.opencontainers.image.vendor
org.opencontainers.image.licenses
org.opencontainers.image.ref.name
org.opencontainers.image.title
org.opencontainers.image.description
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にはない要素 |
application/vnd.oci.image.config.v1+json
を OCI Runtime configuration blobに変換する方法を定義するConfig.WorkingDir
→ process.cwd
Config.Env
→ process.env
※1Config.Entrypoint
→ process.args
※2Config.Cmd
→ process.args
※2org.opencontainers.image.author
に指定する必要があるorg.opencontainers.image.created
に指定する必要があるorg.opencontainers.image.stopSignal
に指定する必要があるConfig.User
→ process.user.*
/etc/passwd
をパースすることによってprocess.user.uid や process.user.gid に値を格納するConfig.ExposedPorts
→ annotations ※1Config.Vlumes
→ mounts
※2org.opencontainers.image.exposedPorts
に値をセットしなければならない
※ 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のテストを実行する環境を構築してみました。以下、それに伴って行なったことの解説になります。
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を実行しているだけです。
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内に含まれています。
`$ 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#
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日
@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には入っていないんですね)
近年のWebアプリケーションインフラ界隈は、Docker、Kubernetesなどによって Cloud Native になっていく流れが主流となっているように感じています。今所属している会社でも、Rails on Docker on Kubernetes with GKE という構成になっており、いわゆる「モダン」と呼ばれる構成からはインスタンスのプロビジョニングという概念が消えつつあります。
Itamaeに限らず、ChefやAnsibleなど書くことになる機会も減っていくでしょう。
ただ、世間の潮流がどれだけ Cloud Native になったとしても、インスタンスの構築という作業が消えてなくなることは絶対にありません。それに、個人レベルで生のマシンの構築を行なったりするという概念もなくなりはしないでしょう。
そのような場合において、Itamaeを選択肢のひとつとして十分に検討対象であるという状況を続けていきたいと考えています。何故なら僕はRubyistですし、構成管理ツールの中ではItamaeが最も好きだからです。
今後のItamaeにおいて、後方互換を失う規模の変更が入るということはないと思いますが、ryotaraiさん、sue445さんと話していてまだまだ改善する部分はあるという認識は共通しています。細かい改善をしながら粛々とメンテナンスが行なわれていくことになるでしょう。
今後よろしくお願いします。
幹事のどくぴーや、前年参加のなっちゃん、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日
来年も参加したいです。もっというなら、他のアカデミックな発表に負けないような濃い内容の発表を持っていきたいと思いますが、さてそんなことができるかな……
夏も終わりですね。
oedo07 - unasuke - Rabbit Slide Show
埋め込みがあまりうまくいかないので上のリンクからお願いします。
スライドの中でも参照したのですが、もともとこれはこのブログの1記事として出す予定のものでした。
ただ、tqrk12のときに大江戸Ruby会議の登壇者を募集していると聞き、じゃあこっちに回してしまえということにしました。
今の僕に理解できるのは、表示が絡まない部分のみなんですよね。GTKで書かれているところに関しては全くわかりません。また、slide.rabbit-shocker.orgも、Rails、Sinatraくらいしか触ったことのない僕にとっては、サッと読んだ程度では理解に相当時間がかかりそうでした。
とはいえ改善点は見えているのでそのうちパッチを送りたいと思います。
PowerPointやKeynoteを用いた、デザインの優れたスライドをRabbitで作成するのは相当大変です。ではなぜ僕がそうするのかというと、発表でも話しましたが、過去の自分の資料が見られなくなってしまったからです。
PDF等に書き出していなかった自分が悪いといえばそれまでなのですが、プロプライエタリではこのようなことも起こりえます。
立てました。
主にMastodonでは末代(@unasuke@mstdn.maud.io)に軸足を置いて色々やっていました。しかし自分自身がRailsアプリケーションの運用を業務で担当しており得意とすることから、やはり自分のドメイン下で動かしてこそだろうと思いsingle user instanceを立てることにしました。(以前にも非公開で運用していたインスタンスはあったのですが、解消できない不具合があり閉じてしまっていました)
Kubernetesの勉強と書いたように、GKEの上で運用しています。費用を抑えるために全てのPodをPreemptible VM instance(最大でも24時間までしか稼動しない)上に配置しています。これはちょっとまずくて、Redisも24時間で自動的に再起動になるのでキャッシュが揮発してしまいます。この辺をなんとかしないといけないなーと思っているので、まだ積極的にリモートフォローはできない状態にいます。
また、このインスタンスでのアカウント作成を開放するつもりはありません。そもそもこのドメイン以下にアカウント作りたいか?という問題、特に今の構成では安定性が保証できないという問題などがあるためです。 5人以上が月に数百円の運営費用を負担してでも、というのであれば検討はしますが、基本的に開放は無いです。
スライドつくりながら、これになることばかり考えている
— うなすけ (@yu_suke1994) 2018年7月8日
第133回 夏休み特別企画・夢の仰向けUbuntu生活:Ubuntu Weekly Recipe|https://t.co/JqWu8qNOVX … 技術評論社 https://t.co/mWT75PMzGv
僕が今使っている椅子は、上京したときに買った安いもので、ずっと座っていると腰が痛くなってくるし、角度によっては酷く軋むので、あまり集中して作業のできるものではありませんでした。
そんな訳でもっぱらベッドの上で作業していたのですが、胡座も長い時間は続けられませんし、寝転がるのも首を上げなくてはならず、どちらにせよ長時間の集中が厳しい状況にありました。(ノート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 でした。上の価格には配送料が含まれたりそうでなかったりしますが、良い椅子(ここではバロンチェアとする)より安価に済みました。
これはメタルラックにディスプレイを固定している部分です。
すごく廃人っぽいです。
総合的には快適です。皆さんもどうですか。
2018年の7月14日はRails Developers Meetup 2018 Day 3 Extreme、 15日は高専カンファレンス in 東京 2018、 16日は高専DJ部 #19、 という非常に詰め込まれた3連休でした。
それぞれでブログを書くほどの気力がもうないので、この1本にまとめて書きます。
発表資料です。
Railsdm 2018 day 3 extreme - unasuke - Rabbit Slide Show
「Railsのissueを毎日読む方法」という題で登壇させて頂きました。
実際のところ、pixivFANBOXでのwatchは結構継続できており、ありがたいことに支援者も6名ほどおられます。
スライド内のコードの一片もなく、技術というよりは心構えに関する発表でした。その割には良いと言ってくださる方がいらっしゃったのはとても嬉しかったです。
うなすけ pic.twitter.com/hL0Rw6I4QA
— ゆーけー / Yuki AKAMATSU (@ukstudio) 2018年7月14日
#railsdm うなすけさんのAMAへのセルフ質問、最高によかったですね
— tatsuosakurai 🍺🤖 (@tatsuoSakurai) 2018年7月16日
ちなみに、Rubyはwatchしていません。mruby/mrubyはwatchしています。理由はRubyの開発の主戦場がGitHubではないからです。
発表資料です。
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日
これはもともとasonasさんが運営の主体だったのですが、高専カンファ in 東京 2018の運営で忙しいということもあり、前回くらいから運営のお手伝いをしています。 あと、今回はDJはしていません。
前日のカンファでも、イベントのさ中でも新規入部希望者がいらっしゃったのはとても嬉しいです。
#kosendj pic.twitter.com/8tty1mueDp
— dachiba (@dachiba) 2018年7月16日
ギュッとなっていて、楽しかったですが、体力の衰えも感じました。今後もやっていきましょう。
RubyKaigi 2018のLTにCFPを提出しましたが、残念ながらnot acceptedとなってしまいました。
なので、その内容を先日開催された表参道.rbで発表してきたのですが、LTにする過程で端折った様々を補完するために記事にまとめました。
発表資料自体はここにあります。
https://github.com/unasuke/omotesandorb-35
mail.gemはRubyでemailを扱うためのgemであり、actionmailerの依存関係にも含まれるように、世界中で使用されているgemです。
mail | RubyGems.org | your community gem host
rails/railsのCIでは、以下に示すように警告が有効になっています。
# 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によって修正されています。
しかしmail.gemについては、そのgem単体で発生しているwarningが多く、修正の手間が大きいのではないかという懸念がありました。
そのようなことを @koicさんや@yahondaさんが #asakusarb にて話されており、偶然その近くにいた僕が興味を持ってやってみようということになりました。
まずは、警告が出ているコードを見てみます。中には確かにインデントの揃っていないcase-whenがありましたが、それよりも僕は次のコードに疑問を抱きました。
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 さんに伺ってみたところ、そもそもそのコードは何らかのツールにより生成されたもののように見える、というアドバイスを頂きました。
自動生成されたコードであるならば、その成果物に対してあれこれ修正するのは再度生成した場合に全て上書きされるので、無意味となります。生成元に対して何らかの対処をしなければなりません。
それでは、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はステートマシンコンパイラのようです。emailのデータをパースするのに使われていそうだ、というところまでわかりました。公式サイトは以下です。
http://www.colm.net/open-source/ragel/
ひとまずRagelをcloneして、内部を眺めてみることにします。
公式にもあるとおり、以下のようにして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日
先程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のようなものがあれば解決します。
そのようなRubyの自動整形ツールとしては、有名なものであればRuboCopが挙げられるでしょう。RuboCopは -a
をオプションとして渡すことで、対応しているCopについては自動で整形してくれます。
しかしRuboCopはあくまでも静的解析ツールであり、自動整形ツールではありません。自動整形の機能についても、誤動作が無いというわけではありません。
用途に合致するものがないか調べていたところ、以下のgemが見付かりました。
https://github.com/ruby-formatter/rufo
rufoはRipperという、Rubyに同梱されているRubyの構文解析器を使用してコードの整形を行ないます。なのでその整形結果に関してはある程度の信頼性があると判断してよいと考え、これを使って整形することにしました。
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
なんとありがたいことに、それほど時間を置かずどちらの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"
よかったですね。