にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

ぜんぶ Docker コンテナにする (HTTPS+IPv6 対応 )

事例が何かの参考になればと思ったので記事にしました。

3行でまとめると

すべて Docker 化

個人開発のサービスを省力運用にするべく、 Zenrei (zenrei.nyamikan.net) をすべて Docker コンテナ化しました。

f:id:no_clock:20200308221840p:plain
構成図

docker-compose.yml とそれぞれの Dockerfile はリポジトリに公開しています。

nginx (host network)

https-portal の節で紹介します。

nginx (bridge network)

フロントエンドの静的ファイル配信と、サーバサイドアプリケーションへのリバースプロキシを担います。マルチステージビルドを使用し、 Vue.js の生成物のみを nginx に持ってきてイメージサイズを 23MB と小さくしています。

view/Dockerfile

FROM node:slim as builder
WORKDIR /app
ADD package*.json ./
RUN npm install
ADD . /app
RUN npm run build

FROM nginx:alpine
ADD nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/dist /app

api

サーバサイドアプリケーションです。 nginx と同様にマルチステージビルドとしていますが、日本語 WordNet の辞書を用いている関係でイメージサイズは 290MB あります。

aaaton/golem 辞書 (Git LFS) を取っている箇所は妥協しています( go build で Git LFS のデータがどうしても取得できなかった)。個人開発は動けばよかろうの精神であまり深追いしていません。

api/Dockerfile

FROM golang:latest as builder
WORKDIR /go/src
RUN apt-get update && apt-get install -y git-lfs && git lfs install --skip-repo
RUN curl http://compling.hss.ntu.edu.sg/wnja/data/1.1/wnjpn.db.gz > wnjpn.db.gz && gunzip wnjpn.db.gz
RUN mkdir -p /go/src && git clone https://github.com/aaaton/golem.git /go/src/golem
ADD . /go/src
RUN go build . && ls -l /go/src

FROM debian:buster-slim
WORKDIR /go
COPY --from=builder /go/src/api /go/src/wnjpn.db ./
CMD ["./api"]

mongo

ソースコード内の変数名の使用実績などを永続化している MongoDB です。こちらは公式イメージをそのまま使用しています。

https-portal

上記コンテナ群だけでは、 HTTPS で提供することはできません。そこで登場するのが https-portal です。 https-portal は、下記の機能を持った nginx のリバースプロキシ Docker コンテナです。

  • 環境変数による複数ドメインのシンプルなリバースプロキシ
  • Let's Encrypt を用いた https 化(証明書は自動取得・自動更新)

通常は nginx や certbot や cron をゴニョゴニョしなければなりませんが、これを使うと docker-compose.yml に十数行書くだけで済みます (Quick Start 参照 ) 。他のオーケストレーションツールを追加せずに Docker / Docker Compose で完結するのも嬉しいポイントです。

version: "3"

services:
  https-portal:
    image: steveltn/https-portal:latest
    network_mode: host
    ports:
      - "80:80"
      - "443:443"
    environment:
      DOMAINS: "zenrei.nyamikan.net -> http://127.0.0.1:8080"
      STAGE: "production"
      LISTEN_IPV6: "true"

IPv6 対応

https-portal の nginx が IPv4 でしかリッスンしていなかったため、 IPv6 でリッスンするようプルリクエストを出しました(マージ済み🎉)。

github.com

IPv6 と docker-proxy

…ただし、 IPv6 でリッスンしなくても、 IPv6 で通信を受け付けること自体は可能です。ブリッジネットワークでコンテナを起動してポートを開放すると、 IPv4 は NAT で、 IPv6 は docker-proxy による IPv4 への変換で、それぞれ通信がされるようです。

$ sudo iptables -L -n
Chain DOCKER (3 references)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            172.18.0.2           tcp dpt:3001

$ sudo netstat -anp
稼働中のインターネット接続 (サーバと確立)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態        PID/Program name
tcp6       0      0 :::3001                 :::*                    LISTEN      9181/docker-proxy

この状態で IPv6 通信を行うと、ブリッジアダプタの IPv4 アドレスが通信元になってしまい、それがアクセスログに記録されてしまいます。

172.18.0.1 - - [08/Mar/2020:04:56:33 +0000] "GET /favicon.ico HTTP/2.0" 200 6183

そこで、ホストネットワークでコンテナを起動しつつ、 IPv6 で nginx をリッスンさせることで、これを解消しました。

240f:<MASKED>:7194 - - [08/Mar/2020:07:01:14 +0000] "GET /favicon.ico HTTP/2.0" 200 6183

もう一度まとめると

参考

CRCチェックつきでAM2320の温湿度を読み取る (Raspberry Pi + Python 3)

温湿度センサモジュール AM2320の温湿度を取得するサンプルコードは数あれど、CRCチェックまでやっているコードが見当たりません。

ということで、データシートを見つつ書きました。

import smbus
import time

i2c = smbus.SMBus(1)
address = 0x5c

# Step one: Wake Sensor
try:
    i2c.write_i2c_block_data(address, 0x00, [])
except:
    pass
time.sleep(0.003)

# Step two: Send the read command
i2c.write_i2c_block_data(address, 0x03, [0x00, 0x04])

# Step three: To return the data read
time.sleep(0.015)
block = i2c.read_i2c_block_data(address, 0, 8)

# Check CRC
crc = 0xFFFF
for i in range(6):
  crc ^= block[i]
  for j in range(8):
    if (crc & 0x0001 == 1):
      crc >>= 1
      crc ^= 0xA001
    else:
      crc >>= 1

if block[7] << 8 | block[6] != crc:
  raise RuntimeError("CRC error")

# Print values
humidity = float(block[2] << 8 | block[3]) / 10
temperature = float(block[4] << 8 | block[5]) / 10

print(humidity, "%")
print(temperature, "°C")
$ python3 read_am2320.py
33.9 %
21.3 °C

参考

Linux コマンドの最長しりとりを求める

Linux コマンドでしりとりをすると、最長でいくつ繋がるのか?」

この疑問を解消すべく、最長しりとりを求めるプログラムを実装しました。

結論

最長しりとり問題

論文「最長しりとり問題の解法」

全パターン網羅して最長を決める… といきたいところですが、単語数が増えてくると数時間掛けても解けません。

調べてみると、なんと論文になっています。整数計画問題として定式化した上で、分枝限定法により解を得るものです。さらには、既に実装済みのWebサービスソースコードまであります。

しかし、 Crystal を触りたい、という別の目的から車輪の再発明をすることにしました。

Crystal 実装

ソースコード

論文を読み解きつつ再発明した実装は、 GitHub に公開しています(ソルバーに GLPK を使用しています)。やみくもに木で探索するものも入っています。

Crystal 言語

Crystal は、 Ruby に非常に似た文法を持つ、型推論の付いた静的型付け言語です。

private def self.find_closed_path(v : Char, x : Hash(String, Int32), k : Int32)
  edges = x.select { |k, v| v > 0 }.map { |e| e[0] }
  find_closed_path_recursively(v.to_s, edges, k)
end

型の指定方法、キャスト、プロパティの指定方法などは公式ドキュメントを参照しましたが、それ以外は Ruby のつもりで書けました。それくらい似ています。

ただし、 gem は使えません( C バインディングは使えます)。「文法が似ている全く別の言語」であることを思い出させてくれます。

結果

各種 Docker イメージの PATH に含まれているコマンドを抽出し、最長しりとりを求めました(いずれも数秒程度で解き終わりました)。

Docker image:tag コマンド数 しりとり長 使用率
alpine:3.10.3 317 173 54.6%
amazonlinux:2.0.20191016.0 261 146 55.9%
centos:8 573 326 56.9%
debian:10.2 409 231 56.5%

なんと CentOS 8 では 300 以上のコマンドが繋がります。 Amazon Linux が Alpine Linux よりもコマンド数が少ないなど、しりとりとは無関係に興味深い点もあります。

以下に、しりとりの内容を記載します。 w または z で始まり、数字で終わっている点はどのディストリビューションも共通です。

Alpine Linux 3.10.3

Length: 173
Shiritori: whoami => ipcs => strings => setlogcons => setkeycodes => swapoff => fgrep => powertop => pmap => pgrep => pwd => depmod => dd => dc => crontab => brctl => lzma => add-shell => login => nologin => nsenter => rmdir => rm => microcom => md5sum => mkdir => reset => timeout => test => ttysize => ether-wake => env => volname => eject => touch => halt => tr => realpath => hdparm => modinfo => openvt => truncate => expr => raidautorun => nl => logger => rfkill => lzopcat => tunctl => lzcat => tail => ln => nmeter => remove-shell => lsusb => bc => chown => nproc => comm => mpstat => tac => crond => dnsdomainname => expand => dirname => ed => deluser => rmmod => date => echo => od => deallocvt => tar => readahead => dumpkmap => passwd => delgroup => ping => gzip => patch => hexdump => pkill => lzop => printenv => vconfig => gunzip => pwdx => xzcat => top => pscan => nc => cksum => mkpasswd => du => unlzma => arping => getopt => true => egrep => printf => fsync => cal => lsof => fdflush => hostid => diff => fbsplash => head => df => fstrim => mesg => getconf => fsck => killall => losetup => poweroff => flock => klogd => dmesg => grep => pidof => findfs => sysctl => ls => setserial => less => sum => mkmntdirs => sha512sum => mkdosfs => su => unix2dos => sync => clear => run-parts => slattach => hd => dumpleases => sendmail => ldconfig => groups => sha3sum => mktemp => ps => sha256sum => mkswap => pipe_progress => sha1sum => mknod => dos2unix => xargs => shuf => fuser => rev => vi => ifconfig => getent => tty => yes => scanelf => fatattr => readlink => kill => lspci => ipcalc => cryptpw => whois => swapon => nameif => factor => rdev => vlock => killall5

Amazon Linux 2.0.20191016.0

Length: 146
Shiritori: zic => curl => ln => nohup => pwd => dd => db_load => diff => fg => gpg => gpg-error => rmdir => rpm => md5sum => mkdir => rpmkeys => sotruss => stat => tzselect => tsort => tset => trust => tput => timeout => test => tabs => split => tty => yes => signver => reset => tr => rpmdb => bg => gsettings => sum => makedb => bashbug => groups => stdbuf => false => ex => xmlcatalog => glib-compile-schemas => sprof => factor => rview => whoami => iconvconfig => getpcaps => shuf => fold => df => find => db_printlog => gdbm_load => dir => read => db_tuner => realpath => hostid => db_recover => rm => mknod => db_stat => truncate => expand => db_checkpoint => touch => head => dircolors => sha512sum => mkfifo => oldfind => db_hotbackup => pldd => db_dump => paste => egrep => printf => fgrep => pinentry-curses => ssltap => pr => rvi => infotocap => printenv => vi => infocmp => pinky => yum => mktemp => ptx => xargs => sleep => python => nice => expr => runcon => numfmt => true => echo => od => du => users => sha384sum => mv => vdir => rpcgen => nl => ls => sha256sum => modutil => lua => applygnupgdefaults => setcap => pk12util => ldconfig => getopts => sdiff => fmt => tail => luac => chown => nproc => cp => pydoc => cut => tic => chkconfig => gdbus => sync => crlutil => localedef => fc => cmp => p11-kit => tac => cmsutil => logname => env => view => wc => certutil => ldd => diff3

CentOS 8

Length: 326
Shiritori: zic => cracklib-unpacker => runuser => rtpr => rmdir => rmmod => dmfilemapd => depmod => dd => dbus-send => db_load => dnf => fg => gpg => groupdel => loginctl => localectl => lastb => busctl => logname => egrep => pmap => pgrep => ps => systemd-tmpfiles => systemd-sysusers => systemd-cgls => strings => ss => sotruss => systemd-delta => as => sum => md5sum => mksquashfs => sha512sum => mkfs.cramfs => sha384sum => mkfs => systemd-run => nologin => newusers => su => users => systemd-machine-id-setup => prlimit => tzselect => tsort => trust => timeout => test => telinit => taskset => tracepath => hash => halt => top => pinky => yum => mktemp => pwunconv => vi => ipcs => swapon => nohup => pwconv => vipw => w => wipefs => sulogin => newuidmap => pwscore => elfedit => type => eject => truncate => echo => objcopy => ypdomainname => evmctl => lesspipe.sh => hostnamectl => login => nl => losetup => pkill => lsns => systemctl => lslogins => sysctl => lslocks => swaplabel => ls => skill => less => setfacl => lnstat => timedatectl => last => tail => ldattach => hostname => ethtool => ldconfig => gpgparsemail => lastlog => gsettings => sg => groups => systemd-hwdb => bg => gapplication => namei => ifcfg => groupmems => systemd-cgtop => pkg-config => glib-compile-schemas => strip => ping => getpcaps => setpriv => vmcore-dmesg => getfacl => lsmem => makedb => bashbug => getopts => systemd-path => hexdump => pwdx => xmlcatalog => gprof => fips-finish-install => localedef => fips-mode-setup => printf => fgrep => poweroff => fsck.cramfs => swapoff => findfs => stdbuf => fstrim => mesg => gpgconf => fsck.minix => xzless => sprof => fold => df => find => dbus-test-tool => lsmod => dwp => pwd => dmsetup => pldd => dracut => tload => dmesg => genl => lsinitrd => db_printlog => gdbmtool => ldd => dbus-uuidgen => nisdomainname => expand => dbus-run-session => nm => mknod => dirmngr-client => touch => hostid => db_hotbackup => pkgconf => fsck => kmod => du => update-crypto-policies => sha256sum => mkinitrd => db_dump => pidof => flock => kill => ld.gold => dhclient-script => tty => yes => sha224sum => mkdumprd => dhclient => true => ex => x86_64-redhat-linux-gnu-pkg-config => gdbus => sha1sum => modinfo => od => dirmngr => read => dir => routef => fix-info-dir => resolvconf => factor => runlevel => logger => rpm => mkhomedir_helper => rm => mkdir => runcon => nsenter => rpmdb => blkid => dbus-monitor => ranlib => blkdiscard => db_tuner => realpath => head => db_recover => rtmon => nice => expr => rview => watchgnupg => gtar => rpmkeys => sleep => pwhistory_helper => routel => ld.bfd => delpart => tr => rfkill => ld => dbus-update-activation-environment => tar => raw => whoami => iconvconfig => gpg-error => rdma => applygnupgdefaults => shutdown => newgrp => printenv => vigr => readelf => fdisk => kernel-install => ln => newgidmap => pivot_root => tee => env => vdir => rdisc => coreutils => sync => command => dbus-daemon => nproc => chpasswd => dmstats => sha512hmac => chmod => dircolors => sha384hmac => chgpasswd => dbus-cleanup-sockets => sha256hmac => cd => db_stat => tipc => chkconfig => getconf => fc => capsh => hwclock => kexec => comm => mountpoint => tac => colrm => mkfs.minix => xzdec => cracklib-packer => rvi => ipcalc => chcpu => update-alternatives => sha224hmac => cksum => mv => view => wc => cp => pr => resolvectl => lsipc => chgrp => ptx => xargs => sha1hmac => chmem => mkswap => partx => xz => zless => shuf => faillock => kdumpctl => lscpu => unxz => znew => whereis => setterm => mkfifo => objdump => p11-kit => tracepath6

Debian 10.2

Length: 231
Shiritori: znew => wc => ctrlaltdel => ldconfig => groupmod => dd => diff => fgrep => paste => e2image => expr => runuser => rmt-tar => rmdir => rm => md5sum => mkfs.cramfs => ss => swapon => nologin => newusers => switch_root => tzselect => tsort => tset => tput => timeout => test => taskset => tarcat => tune2fs => stat => tabs => su => users => split => tty => yes => sum => mkfs.bfs => sha512sum => mkfs => sha384sum => mke2fs => sha256sum => md5sum.textutils => sha224sum => mkhomedir_helper => runcon => nsenter => rtstat => tr => run-parts => sha1sum => mkdir => resize2fs => sort => tar => rtcwake => e4crypt => truncate => expiry => ypdomainname => e2mmpstatus => script => true => env => vigr => renice => egrep => pr => rgrep => pager => rtmon => nohup => ptx => xargs => sulogin => newgrp => printf => factor => routef => fsck.cramfs => swapoff => findfs => stdbuf => fold => df => find => dash => hostid => delgroup => pwd => dumpe2fs => shred => dircolors => setsid => debugfs => sed => du => usermod => dpkg-deb => b2sum => mknod => debconf => fstrim => mklost+found => dpkg-trigger => readprofile => expand => dpkg-maintscript-helper => realpath => head => debconf-set-selections => sleep => policy-rc.d => debconf-apt-progress => setcap => pldd => dir => rbash => hostname => echo => od => dpkg => groupadd => dmesg => getconf => filefrag => gzip => ping => groups => shadowconfig => groupmems => sg => getpcaps => savelog => getopt => tzconfig => gpasswd => debconf-copydb => bashbug => getent => toe => e4defrag => gunzip => pidof => faillog => groupdel => lastlog => genl => login => nl => losetup => perl => lsattr => routel => logger => rename.ul => lsns => swaplabel => lscpu => userdel => ldd => deluser => remove-shell => localedef => fmt => tail => ln => namei => installkernel => lslogins => shuf => findmnt => tempfile => e2label => lslocks => setterm => mesg => grep => passwd => deb-systemd-helper => rdma => add-shell => lsipc => chown => nproc => comm => mountpoint => tipc => cksum => mount => tic => choom => mktemp => prlimit => tc => chmem => mkswap => pivot_root => tac => cppw => wdctl => lsmem => mv => vipw => wipefs => sync => chcpu => useradd => debconf-show => wall => ls => sdiff => fdformat => tee => e2freefrag => getcap => pwunconv => vdir => raw => whoami => install => lastb => badblocks => start-stop-daemon => numfmt => touch => hwclock => killall5

ふたたび結論

紅葉スポットと紅葉状況をツイートから推定する

ひとり開発 Advent Calendar 2019 10日目の記事です。

成果物: ソーシャル紅葉見頃情報

Webサービスソーシャル紅葉見頃情報」を作りました。

f:id:no_clock:20191209230023p:plain

動機: 紅葉情報サイトの「見頃」表示が信用できない

以下の写真は、複数の紅葉情報サイトで「紅葉状況:見頃」だった時の御岳昇仙峡(山梨県)の様子です。

f:id:no_clock:20191209230525j:plain

正直、見頃はまだ先だと感じました。Twitterを調べても同様の感想を持った方が多いようで、甲府市観光課も「全体的に3分〜4分付き」と表現されている状況でした。

せっかく紅葉を見に行くなら、見頃のピークに行きたいものです。

しかし、紅葉情報サイトの「見頃」は判定が緩いようで使えません。Twitterで流れているツイートのほうが、はるかに信用できそうです……… ということは、ツイートを活用すれば、より厳しく「見頃」を判断出来る可能性がありそうです。

作ることにしました。

解説: サービス構成

サービスの全体像は以下の通り。ぜんぶ書き慣れている Ruby で楽しく書きました。

f:id:no_clock:20191208204338p:plain

それぞれ順を追って説明していきます。

ツイート収集

「紅葉」「見頃」「色づき」などのキーワードで、紅葉状況と思わしきツイートをひたすらに収集します。

紅葉スポットの自動抽出

紅葉スポットは、それこそ ウォーカープラスの紅葉情報ウェザーニュースの紅葉Ch. から集める方法もありそうですが、依存したくないので ツイートから紅葉スポット名も抽出 します。

形態素解析: MeCab + mecab-ipadic-NEologd

ツイートを形態素解析エンジン MeCab に掛けて 固有名詞 を拾います。辞書には、最新の固有表現が多数採録されている mecab-ipadic-NEologd を用います。例えば、福島県会津地方にある景勝地塔のへつり」も正しく抽出できます。

# 標準の辞書 (mecab-ipadic)
$ echo "福島県南会津郡下郷町、塔のへつり。" | mecab -d /usr/local/lib/mecab/dic/ipadic/
福島  名詞,固有名詞,地域,一般,*,*,福島,フクシマ,フクシマ
県  名詞,接尾,地域,*,*,*,県,ケン,ケン
南会津  名詞,固有名詞,地域,一般,*,*,南会津,ミナミアイヅ,ミナミアイズ
郡  名詞,接尾,地域,*,*,*,郡,グン,グン
下郷  名詞,固有名詞,地域,一般,*,*,下郷,シモゴウ,シモゴー
町  名詞,接尾,地域,*,*,*,町,マチ,マチ
、  記号,読点,*,*,*,*,、,、,、
塔の  名詞,固有名詞,地域,一般,*,*,塔の,トウノ,トーノ
へ  助詞,格助詞,一般,*,*,*,へ,ヘ,エ
つり  名詞,一般,*,*,*,*,つり,ツリ,ツリ
。  記号,句点,*,*,*,*,。,。,。
EOS
# 今回使用する辞書 (mecab-ipadic-NEologd)
$ echo "福島県南会津郡下郷町、塔のへつり。" | mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/
福島県  名詞,固有名詞,地域,一般,*,*,福島県,フクシマケン,フクシマケン
南会津  名詞,固有名詞,地域,一般,*,*,南会津,ミナミアイヅ,ミナミアイズ
郡  名詞,接尾,地域,*,*,*,郡,グン,グン
下郷町  名詞,固有名詞,地域,一般,*,*,下郷町,シモゴウマチ,シモゴーマチ
、  記号,読点,*,*,*,*,、,、,、
塔のへつり  名詞,固有名詞,一般,*,*,*,塔のへつり,トウノヘツリ,トーノエツリ
。  記号,句点,*,*,*,*,。,。,。
EOS

ここから固有名詞を拾って、「福島県」「南会津」「下郷町」「塔のへつり」が紅葉スポットの候補となります。

都道府県推定

紅葉スポットの候補に対し、都道府県を推定します。

方法はシンプルで、『京都の南禅寺』のように都道府県名が一緒になっているものを拾い上げ、その最頻値を推定結果としているだけです。ただし、「高尾山口」が「山口」になったり「東京都」が「京都」になったりしないよう、一部正規表現で避けています。

ノイズ除去

紅葉スポットの候補から、ノイズと思われる名称を除外します。

  • 都道府県の推定が分散しすぎている場合
    • 例えば「植物園」は全国にあり、特定のスポットを指していないので除外したい
  • 半角文字を含むか、漢字が1文字も含まれない場合
    • 「1日」「エモい」なども固有名詞として候補に挙がっているため除外したい

紅葉状況の推定

ここまでで紅葉スポットの候補が定まったため、紅葉状況を推定します。

スコア算出

ツイートに含まれる「紅葉状況っぽいキーワード」を抽出し、 0(青葉)〜1(見頃)〜2(落葉) の間で数値化します。

正直ここはかなりゴリ押しです。たとえば 「早かった」は0.4、「もう少し」は0.7、「ちょうど」は1、 のようにで数値を設定しています。複数あれば平均を取ります。

Webサービス

これで紅葉スポット・紅葉状況の情報が揃ったため、Webサービスとして提供します。息を吸うようにフロントエンドを書ける人間ではないので、古き良き非SPAでの実装です。

非紅葉スポットフィルタ(雑)

ノイズ除去をしても紅葉スポット「でない」固有名詞が大量に残るため、ここでフィルタします。ひたすらキーワードでフィルタを掛ける一時しのぎで、改善の余地が残る部分です。

place !~ /^(.+観光客|外国人.+|最盛期|今日この頃|.+気温|何度|四季桜|可能性|寒い朝|世界遺産|誕生日|今シーズン|黄緑|[0-9]+|皇室献上|.+観光|お久しぶり|開催中|全国的|敷地内|温暖化|五平餅|お勧め|青紅葉|.+|.+|.+流星群|.+|.+旅行|暑さ|.+|桜の木|桜を見る会|ロケ地|.+公開|女子力|仙山線|お題|お姉ちゃん|外国人|私たち|朝活|好きだ|平野部|数年|異常気象|トロッコ列車|知らんけど|山手線|予防接種|午前中|募集中|.週間|観光.+|冬桜|落葉高木|今月末|.+市内|目的地|質問箱|.+|.+年前|リア充|.|分からん|.+希望|オフ会|聖地巡礼|子どもたち|大嘗宮|代表者|落羽松|つけ麺|定点観測|入場料|競馬場|.|.|.?週末|冷たい雨||お客さん|時間帯|)$/

検証: 紅葉状況の推定結果

約3週間動かしたので、推定結果を見ておきます。と言っても、Twitterのツイート群からの推定結果とツイートを比べるので出来レースです。

高尾山

f:id:no_clock:20191208212934p:plain

東京 高尾山の推定結果ですが、11/27頃から見頃に近く、12/7頃に見頃を過ぎようとしているようです。高尾ビジターセンターのTwitterアカウントと比べてみます。

11/16 見頃間近

11/24 見頃

12/1 見頃

12/6 落葉進む

推定結果「11/27頃から見頃に近く、12/7頃に見頃を過ぎ」は実際の様子とかなり近そうです。

東福寺

f:id:no_clock:20191208212922p:plain

京都 東福寺は、11/23〜11/30頃で見頃間近〜見頃を行ったり来たりしています。12/1以降は見頃過ぎのようです。公式Twitterアカウントがないため、ユーザのツイートと比べてみます。

11/16 見頃間近

11/23 見頃

11/27 見頃

12/1 落葉始まり

12/7 ほぼ終わり

推定結果「11/23〜11/30頃で見頃間近〜見頃」と期間的には合致しました。スコアがもう少し安定していれば、良い結果と言えそうです。

課題

概ね出来上がりましたが、まだまだ課題は多いです。

  • サンプル数の不足。マイナーな紅葉スポットではほとんど推定出来ない
  • 紅葉スポットのノイズ。紅葉スポット以外が大量に含まれている
  • 紅葉以外への対応。春までに桜の開花状況に対応したい

まとめ

  • 紅葉スポットと紅葉状況を推定するWebサービスを開発した
  • 有名所の推定精度はそれなりにありそうだった
  • 精度向上や桜への対応なんかは今後の課題

ソースコードGitHubに公開しています。

私の転職活動は中学生から始まっていた

SIer脱出を語る Advent Calendar 2019 8日目の記事です。

「アウトプット大事」というHow toの話しかしません。 転職の経緯や転職後については SIerから自社サービス系に転職して半年ちょっと経過した (2019/05/15) をご覧ください。

目次

どんなアウトプットをしてきたか

現存するものを列挙するとこんな感じです。古いものだと、中学生の頃に公開したものもあります。

ソフトウェア

その他

アウトプットに救われた転職活動

「それ、職務経歴書に書いたほうが良いですよ」

初の転職活動は、右も左も分からず転職エージェントを2社活用しました。印象的だったのはIT特化のエージェントでした。

プログラミングを始めたきっかけから今後どうしたいかまでを洗いざらい話した後、こう言われました。

職務経歴書を1ページ増やしても良いので、業務外の活動やSIerで自主的にやっていたこと(Demo or Die(SI現場でいろいろ作った話))をもっと書いたほうが良いと思います

職務経歴書に業務外の活動、という矛盾するアドバイスでしたが、結論から言えばこれに救われました。

私に求められていたもの

転職先はいわゆるWeb系とか自社サービス企業と呼ばれるところですが、現職のメンバ(ソフトウェアエンジニア)には次の特徴があると感じています。

  • とにかく技術が好きで、吸収が早い
  • プロダクトやビジネスに興味を持っている
  • 当事者意識が強い

裏を返せば、メンバとして加わることになる私にも、これらが求められていました。

SIerのギャップとアウトプットのフィット

振り返ると、所属していたSIerでの「普段の仕事」は、求められていたものと大きなギャップがありました。

  • 属人性を排除し、標準化された技術を使う
  • 作業を細分化するか、細分化された作業を仕上げるかのどちらか
  • プロダクトやビジネスに影響を与えにくい

エージェントの方のアドバイスを受けて、上記の仕事にはほぼ触れず「業務外の活動やSIerで自主的にやっていたこと」を中心に据えてアピールをしたわけですが、それこそが「求められていたもの」に近いものでした。

それが評価されたことで、無事転職に至りました。

中学生の頃から始めていた業務外でのアウトプットが、転職に繋がりました。つまり、私の転職活動は中学生から始まっていたのです。

めでたしめでたし。

おまけ: アウトプットでの心がけ

私がアウトプットで心がけているのは2点です。

やりたいことをやる

「やらなきゃ(義務感」は辛くなるので、やりたいことをやってアウトプットにしています。

ポイントは「如何にやりたいことを増やすか」です。情報を仕入れたほうがやりたいことが増えるケースもあります。SIerの頃はWEB+DB PRESSSoftware Designを読んだり、Podcastを聞いたり(Rebuild.fmMisreading Chat、その他)していました。

最近は現職のメンバから刺激を受けることがめちゃくちゃ多いです。

稚拙でも良いので、正しさとリスペクトを意識する

ブログやQiita記事の場合は、次の2点を意識しています。

  1. 公式ドキュメントなどで正しさを確認する
  2. 参考にしたものや引用元を明記する

巨人の肩の上に立つ」という表現がありますが、それです。

かなり時間を使ってしまうことも多いですが、もっと良い方法を見つけたり、間違っていることに気づいたり、デタラメ記事の作成を避けたり出来るので、メリットは多いです。


さあ、アウトプットを増やしていきましょう。

Fargate Spotのお値段比べてみた

Fargate Spotが発表された.

aws.amazon.com

70%ディスカウントは魅力的であるものの,EC2と比べて高いのか安いのかがわからない.ということで表にした.

タイプ vCPU Mem EC2
オンデマンド
EC2
スポット
EC2
リザーブ
(1年前払い)
Fargate Fargate
Spot
m5.large 2 8 0.124 0.035 0.081 0.145 0.044
c5.large 2 4 0.107 0.033 0.072 0.123 0.037
r5.large 2 16 0.152 0.037 0.089 0.190 0.057

※ap-northeast-1(アジアパシフィック (東京)),2019年12月5日(木)23時時点,単位は USD/h

厳密には, C5 は M5, R5 とプロセッサのクロック周波数が異なるし,そもそもFargateのプロセッサシリーズは公表されていない点に注意.

見づらい表になってしまったが,Fargateはメモリ単価が少々高いという印象.

気象庁の天気予報文をパースする ☀☁☂

天気予報文

気象庁の天気予報には 予報文 があります。

北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨

気象庁 | 天気予報 : 東京都 (10/20 11:00発表 21日の天気予報文)

やや難解ですが、 気象庁|予報用語 と照らし合わせることで下記であることが分かります。

時間 天気
0〜15時 くもり 時々 晴れ
15〜21時 くもり
21〜24時 くもり 所により 雨

天気予報文と正規表現

ここからは根拠のない独自研究です。

文法

気象庁が発表する天気予報文には 文法 が存在し、どの予報文もいずれかの文法にマッチします。以下は例です。

文法
天候 晴れ
天候 頻度 天候 晴れ 時々 くもり
天候 時間帯 天候 くもり 昼過ぎ から 夕方
天候 時間帯 頻度 天候 くもり 昼前 まで 時々 晴れ
天候 頻度 天候 時間帯 天候 晴れ 時々 くもり 夜のはじめ頃 から
天候 所により 天候 くもり 所により
天候 所により 時間帯 天候 くもり 所により 明け方 まで
天候 所により 修飾 所により 雷を伴い 激しく 降る
天候 所により 時間帯 修飾 所により 夕方 から 雷 を伴う
天候 頻度 天候 時間帯 天候 所により 時間帯 修飾 晴れ 時々 くもり 夕方 から 所により 夜遅く 雷を伴い 激しく 降る

正規表現

文法を正規表現で表すと、次のようになります(雪 後 雪などのありえない表現もマッチしますが、今回は無視します)。

(晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)( (時々|一時|後) (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)( (時々|一時))? (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る))?( (所により|[^ ]+ では)( ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?))? ((晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)|(雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)))?

何を言っているんだという感じなので、擬似的に表現をすると次のようになります。

天候( 頻度 天候)?( 時間帯( 頻度)? 天候)?( (所により|地名)( 時間帯)? (天候|修飾))?
項目 正規表現
天候 (晴れ|くもり|雨|雪|雨か雪|雪か雨|雪 で ふぶく|雨 で 雷 を伴う|雨 で 雷を伴い 激しく 降る)
頻度 (時々|一時|後) ※時間帯の後ろは (時々|一時)
時間帯 ((朝晩|朝夕|夜)|(未明|明け方|朝|昼前|昼過ぎ|夕方|夜のはじめ頃|夜遅く)( (まで|から( 未明| 明け方| 朝| 昼前| 昼過ぎ| 夕方| 夜のはじめ頃| 夜遅く)?))?)
地名 気象庁 | 気象警報・注意報や天気予報の発表区域 の「市町村等をまとめた地域」に準じる
修飾 (雷 を伴う|激しく 降る|雷を伴い 激しく 降る|霧)

※「雪 で ふぶく」等は修飾が入っていますが、簡単のために天候にまとめています。
※カバー出来ていない天気予報文が存在する可能性があります。

テスト

下記サイトで試してみるとマッチします。

f:id:no_clock:20191021004546p:plain

天気予報文とパーサ

正規表現のままパーサを書くのはつらいため、より簡単にして概ね次のルールでパーサを書きますました。

  • 入力は予報文、出力は0〜21時の3時間ごとの予報
  • 「時間帯」「所により(地名)」「何もない(終端)」が出現した場合に分割
  • 「所により」「頻度」が含まれる場合は予報を追記。それ以外は指定時間帯の予報として上書き

動作

$ ruby runner.rb

天気予報を入力: 晴れ 時々 くもり 夕方 から 雨 所により 夜遅く 雷を伴い 激しく 降る
解析結果:
{:wind=>nil,
 :weather=>
  {0=>"晴れ時々くもり",
   3=>"晴れ時々くもり",
   6=>"晴れ時々くもり",
   9=>"晴れ時々くもり",
   12=>"晴れ時々くもり",
   15=>"雨",
   18=>"雨",
   21=>"雨所により雷を伴い激しく降る"}}

天気予報を入力: 北東の風 日中 東の風 くもり 昼過ぎ まで 時々 晴れ 所により 夜遅く 雨
解析結果:
{:wind=>"北東の風 日中 東の風",
 :weather=>
  {0=>"くもり時々晴れ",
   3=>"くもり時々晴れ",
   6=>"くもり時々晴れ",
   9=>"くもり時々晴れ",
   12=>"くもり時々晴れ",
   15=>"くもり",
   18=>"くもり",
   21=>"くもり所により雨"}}

3時間ごとの天気予報がわかるようになりました。めでたしめでたし。