年中アイス

いろいろつらつら

GoでSSH port forwarding

今までPort Forwardingはbashrcに書いて呼び出していましたが、量が多い時など手間が多くなっていたので、Goで呼び出せるようにしようと調べてみました。ちょうど同時期にGoで実装している方がいたので、参考にしてサンプル実装を作ってみました。

go-memo/ssh_port_forwarding at master · reiki4040/go-memo · GitHub

vagrantで踏み台(bastion)とHTTPサーバを起動して、bastion経由でHTTPサーバの80ポートにforwardingする形でサンプルを作っています。

参考にしたページではSSHの認証がパスワードだったので、公開鍵認証に変更して実装しています。この公開鍵認証の実装例がGoDocにあるんですが、その通りにやると動かず地味にはまりました。

調べてみたらExampleはここの一部とのこと。HostKeyをデフォルトでチェックしてないGoの脆弱性(CVE-2017-3204)があってそうなったようですね。とりあえず、サンプルではそこまで要らないのでInsecureIgnoreHostKeyで対応しています。実際使うときは、適宜チェック機構があった方が良いです。多分known_hostsファイルの対応でいいと思います。

実装してないので、ツール的に必要だと思うのは以下あたりなので適宜実装していこうかと。

  • 複数のport forwardingの一括起動
  • remote hostのDNS解決(A record, SRV record)
  • 定期的な接続先の再解決(DNS追従など)
  • ServiceDiscovery的なもので自動解決(AWS CloudMapあたり)
  • 暗号化されているprivate keyの読み込み対応(GoDocにもありますがx509でやる必要がある様子)
  • remoteが複数ある場合のバランシング(これはできるんだろうか?)
  • 多分色々エラーハンドリング

参考

LINEのLive Photo投稿をOFFにする

今月2018/11の後半ごろに、妻からLINEの写真の拡大表示時にダウンロードが発生するようになって面倒だという指摘をもらいました。

投稿写真にLiveという文字が入るようになったので、気づいてはいたのですが、Live Photo(iOSの写真の機能で、シャッター前後の動きがみれる写真)の送信に対応したようです。ただ、調べてみたら、Live Photoの送信自体は、2018/05のアップデートからのようです。

しかし、最近2018/11前半までは、デフォルト静止画として投稿されていました。これはトークルームの写真一覧で確認しました。特にLive Photoである必要のない写真も多いので、LINEで送信時にOFFにする方法はあるのかなと思い、トークルームの設定を見ましたが、特に見当たらず。試しに、写真選択画面でサムネイル左下の二重(三重?)丸太陽みたいなアイコンをタップすると斜線が入り、OFFにすることができました。

ただ、現状デフォルトONで、自分でOFFにする必要があるオプトアウト方式になっています。アプリ全体かルーム単位ぐらいでデフォルト共有ON/OFFを選びたいです。

※LINEから直接写真を撮る場合はLive Photoにはなりませんが、iPhoneのカメラで撮った写真をライブラリから投稿する場合にこれに該当します。

事故に注意

Live Photoが共有できるようになったこと自体は良いのですが、今まで静止画として送られていたものが、Live Photoとして送られるように変わっています。これによって意図しないプライバシー情報の漏洩を起こす可能性があるので気をつける必要があります。

Live Photo自体は、前後の状況が残るので、シャッターチャンスを広くとるために使うケースもあると思います。編集で代表の画像を選べるので、動きのある被写体を撮る場合に重宝します。

しかし、頻繁にON/OFFを切り替えるものではないので、常にLive Photoで撮影している場合に、前後に共有する際に気をつけるもの(写り込んだ人や、部屋の一部や窓の外、意図して隠している部分など)が写っていた場合に、それを意図せず送信してしまう事故が起こり得ます。

androidの友人に送ってみたら、ムービーとしては見えないというので、iOS系の対応機種だけかもしれませんが、注意しましょう。

Kindle Paperwhite 2018購入しましたが…

新しいkindleを購入しました。ちょうど4年前ぐらいにvoyageを買ったんですが、最近、容量不足と若干バッテリーがへたり気味かなと思い、買い替えを検討していたところに、ちょうど発表されたので予約しました。購入理由は、容量がvoyageの8倍、おまけで防水機能付きになったという点です。oasisは、さすがに高すぎて対象外です。

ただ、最初に書いておくと、個人的にvoyageのほうが読書体験が良いという感想です。良くも悪くもPaperwhiteの正統進化系でした。

使用歴と課題

Paperwhite(2014) -> voyage(2014) -> 今回Paperwhite(2018)です。カラーや大判のものはiPad mini 4で見ています。

一番大きな課題は、容量不足です。漫画はもう余程のものでない限りは、kindle版しか買わないことにしています。そのため、voyage(4GB)で、新しい本のダウンロードが止まっていました。古いものは消しますが、あー、ちょっと読みたいと思った時に、じゃどれか消して、数分待ってねってのは、ストレスです。iPadは容量はありますが、文字や白黒ばかりを見ると、やはりE-ink端末のほうが目が疲れません。

バッテリーは数日は持ちますが、年数考えると次のを検討してもいいかなと思いました。防水は、頻度は低いですが、お風呂でそのまま読めるとziplockの手間がないなーという程度です。

Paperwhite 2018

購入したのはKindle Paperwhite 2018 WiFi/広告なし32GBモデルです。容量は初期状態で27GB弱でした。

一通り持っている漫画等をダウンロードしたところ(すごく時間かかった)残りは9.47GBとなっており、すでに2/3の18GB弱を使っています。voyage(4GB)で足りないはずです。防水機能は、まだ試していません。

操作は画面タッチのみで、物理ボタンは下部の電源のみです。丸みのある形状も特に変わり無さそうです。スペック的には以前のPaperwhiteより軽くなっています。

個人的なvoyageの優位点

voyageの後に、Paperwhite使って感じたのは、「進む」と「戻る」を、あまり手を動かさずに行える、というのが思った以上に大きかったことです。

voyageの場合、左右のベゼルの真ん中あたり(持つと触れる部分)に進むボタン*1があり、持っている状態で少し押し込むと、次に進むことができます。戻るはその少し上にあり、これも少し上に親指を動かして押し込む動きになります。

Paperwhiteの場合は、画面に触れる必要があり、指を横に動かすことに加えて、左向きフリック、右向きフリックと使い分ける必要があります。これが毎ページとなると、大きな差になります。また、本のリスト時は、リストを進めたい時に、別の本を誤って開いてしまうことがあり、ストレスです。

まとめ

voyageは、価格高めだと思っていましたが、Paperwhiteに戻ってみて読書体験の良さを再認識しました。しかし、販売ラインナップからは消えてしまいました。容量少ないのが不満なので、32/64GBあたりで新機種+価格下げて出し欲しいですが、Paperwhite 2018と真正面からぶつかるので無さそうです。

Paperwhiteしか使ったことのない人は、そのままなので特に違和感はないかと思います。voyageまたはoasis常用してた方は、このボタンの差は大きいかもしれません。

*1:正確にはボタンというよりは、iPhoneの押せないけど押してる感のあるホームボタンと同じ?感触

韓国でSIMを購入してiPhoneを使う

韓国で現地SIMを買って、iPhone7 SIMフリー版に入れて過ごしたので、諸々メモです。

今回の要件は、以下です。

  • 滞在期間が不定(これは十分には満たせませんでしたが)
  • 普段使っているiPhoneを使いたい
  • バッテリーの管理や通信の利便性を考慮してSIM自体を手に入れたい

購入

少し急いでいたので、あまり複数のプランを比較はできていません。購入したのはSKTというキャリア*1の10days SIM。金浦空港の出口を出て左手奥に、購入カウンターがあり、そこで購入ができました。特に事前予約はしていません。

詳細や他のプランはSKTのページ(上部に日本語表示選択あり)を参照ください。

データのみの10日間*2で1GB/日までの通信量です。電話番号は付いていて、受けることはできますが、掛けることはできない*3ようです。日本円でおよそ4000円でした(当時レート)

個人的には、今回滞在期間が不定だったのと、1GB/日も要らないので、期間が長く総データ量に制限がある方が良かったです。30日間通信量10GBまでみたいなの。

モバイルルータもレンタルできるようで、何人か借りていました。多分そっちは要予約。簡単な日本語と英語ができる人がいるようで、私は英語の人に当たりましたが、プラン一覧はボードがあるのでそれを見ながらやれば、そんなに苦労しないと思います。

支払いはクレジットカードで行いました。特にパスポート等の身分証明の確認はなく、SIMのパッケージをレジ通してカードで払うというだけでした。

SIM交換

その場で、iPhoneSIMカードを交換して、通信の確認を行うところまで求められます。

  • iPhoneのSIMトレイを出す
  • SIMを交換する
  • iPhoneを再起動する(何回かすることも)

日本で使ってるSIMを取り出すので、それを無くさないようにしないといけません。ピンはSIMパッケージに含まれていますが、交換したSIMを入れるものは自分で用意が必要です。これ忘れがちで、私は財布の小銭入れに入れておきました。

SIMカードを交換すると、restartと言われるので、iPhoneを再起動します。圏外の文字が出ていると、service out?(この文字は使えないって意味なの?)と聞かれるので、yesと答えるともう一度restrartと言われるので再起動。

認識すると、上のバーにSKTの文字が表示されます。そしたら、カウンターの人に一旦渡して、認証作業?を行うようです。個人的にはここで、私に画面見えないように操作するのはやめてほしいんですが。。。私は日本で使っているOCNの通信プロファイルを消してくれと言われました。(これが本当に必要だったかは不明)その後、SMSでWelcome to SKTのメッセージが来て、電話番号も通知されます。自分でもブラウザなどで特定のページが開けるかを確認して完了です。

感想など

用事の関係もあり、そんなに動き回っていませんが、ストレスなく使えました。韓国はカフェなどにWiFiが当たり前のようにあり、レシートにWiFiの情報が載っていることが多いので、もし通信量が気になる場合は、そういうところや宿泊施設のWiFiを使うのが良さそうです。

結局、10日以上滞在することになったので、SIMは途中で期限が切れました。1日前ぐらいにもうすぐ期限切れるよと、SMSが来ました。ちなみにSIMの期限を延ばすことは、このプランだと不可でした。消したOCNのプロファイルは、宿泊施設のWiFiを使って再度インストールしてから帰国し、無事動作しました。

滞在期間が決まっていて、複数人の場合は、モバイルルータを借りる方が便利かも知れません。ただ、モバイルルータの場合、WiFiと認識して、iPhoneのアップデートを自動で行う設定に、通信量を食われることがあるので注意が必要です。SIMを入れ替えると、iPhoneだけ気にしていれば普段通り使えて、モバイルルータの電池残量を気にしなくて良いです。また、モバイルルータと違い、返却が不要なのも便利な点ではあります。

しかし、SIM交換は面倒ではあるので、iPhone XSに搭載された、eSIM*4の各国のプラン拡充に期待したいところです。

*1:日本でいうとdocomo的な位置づけらしいです

*2:24h*10daysなので、11:00AMに使い始めると、10日後の11:00AMが期限です。

*3:相手に着信履歴だけ残せるとか言ってた気もしましたが使ってないので詳細不明

*4:物理的な交換ではなく、ソフトウェアで通信プランを書き換えることができる機構

ECSのコンテナ間でNLBを経由した通信がつながらないことがある

gRPCで作った複数のサービスを、同じECS Cluster上で動かし、他のサービスを呼び出している時に、通信できなくなったことがあったので、その原因のメモです。

直接的な原因は、「NLBは、接続元と接続先が同一のIPへの通信ができない」という仕様に意図せず該当したことでした。

このドキュメントにあるように、ヘアピン、ループバックはタイムアウトしますと記載があります。

Internal load balancers do not support hairpinning or loopback.

EC2で直接動作させている場合は、同一ホストに複数のAPIなどを乗せて他のインスタンスとつなぐことは少ないのかと思います。*1しかし、ECSがBridgeモードの場合に、同じインスタンスの別ポートで、複数のコンテナが動くことがあります。その時にこの問題が発生します。*2

例えば、10.0.0.10のECS node instance上で、以下のコンテナが動いていたとします。

  • APIがport 30000で動作
  • 画像用の内部APIがport 30001で動作

この時に、APIが画像APIに対してリクエストを行うと、10.0.0.10 -> NLB -> 10.0.0.10:30001とリクエストがルーティングされることになり、前述の原因に該当します。この状態がBridgeモードでのネットワークの下で行われるのでとても気づきにくいのです。

せめてタイムアウトじゃなくて何かエラーを出してもらえると気付きやすいのですが、TCPレイヤの話なので明確にこのエラーを伝える術がないのかなと思ったりもします。

回避策はドキュメントの回答にあるように、network modeをbridgeではなく、awsvpcモードにして、コンテナごとにIPアドレスが割り当てられるようにすることです。ただ、awsvpcモードはタスクごとにENIを消費し、node instanceのtype/sizeごとに上限が決まっているので、コンテナの集積度が一気に下がります。

instance typeごとのENI上限awsvpcモードでの考慮点を見るとc4.largeでも3個です。1個はそもそもインスタンス自体が使うので、-1した数が、そのインスタンス上で動くコンテナの上限になります。そうなると、c4.largeだと2コンテナしか動かせず、1コア2GB弱/コンテナの割り当てになり、Goの薄いAPIみたいなのだとそんなにリソースいらないからもっと乗せたい、となるのです。

先日、ECS Service DiscoveryがTokyoにも来たので、それを使ってNLBを介さずに、sidecarとしてclient side balancingをやるのがいいのかなと考えています。

終わりに

こんなことを書いたんですが、一足先にクラスメソッドさんも同じことを書いていて ECSがたくさん使われ始めて顕在化するようになったのかなと思います。1日空けて再度内容確認してから投稿しようとしてたら、かぶることに。。。

*1:動かすことはあっても、同一EC2上instance上で動作させるなら、NLB介さずにlocal port経由する方がシンプルです

*2:ECS Clusterに属するnodeが少ない場合に、発生しやすくなり、数十以上あると、まれに発生する奇妙な状態になって気づきにくそうです。

hakoへのプルリク その2

前回に引き続きcontainer health checkの対応をプルリクしました。

github.com

設定追加するだけだし、簡単だーと思ってましたが、何箇所か指摘頂いたので、備忘録。 簡単かなーと思ってましたが、危うく変な挙動を仕込むところでした。

設定値はオプションのものがある

まず、container health checkは以下の設定項目があります。 HealthCheck - Amazon EC2 Container Service

  • command
  • interval
  • retries
  • timeout
  • start period

これらのうち必須なのは、commandだけで、あとはなければデフォルト値が使われます。ですが、hako上必須扱いになってしまっていて、記述を忘れるとエラーになってしまう状態でした。

これに対しては、記述なしに対処する修正をしました。

TaskDefinition更新の差分確認があり、デフォルト値が必要

前述のオプションを対処して、動作確認していました。 すると実はhakoがTaskDefinitionを更新するか確認している部分で、AWSからはデフォルト値、hakoからは値なしで差分確認してしまい、差分がないのにTaskDefinitionを更新ありと判断して、更新、デプロイされる状態になっていました。

自分で同じ設定の時にデプロイし直さないので、気づかず見落とし。 オプションでデフォルト値があるものは、デフォルト値をhako側にも入れるように修正しました。

とりあえずこれでcontainer health checkの追加自体は完了。

exampleをどうするかで少し悩む

最初一番のexampleのhello.jsonnetに追加してましたが、container health checkはオプションだし、他のsidecarとかあるとわかりにくいかもだし、分けるかなーと夜中プルリク書きながら考えてました。(その日は眠くてexampleの書き直しは未実施)

翌日、実際分離してみると、HTTPサーバなのに外からのアクセス設定がなく、container health checkだけあるという奇妙なexampleになってしまったので、hello.jsonnetに残すままにしました。

所感など

設定値の必須/任意のところは単純なミスでしたが、TaskDefinitionの差分のところは運用面で影響がありえる*1ものだったので、見つけてもらってよかったです。

v3の話もissueに載っていてうちで少し変更を要するのはyamlの廃止予定。と言ってもCodeBuildでテンプレートから生成しているので、そこ変えればいいかなというぐらいですが。あんまりjsonnetで何かするような設定がないので、まだyamlのまま移行はしてないです。

あと未プルリクなのは、既存のALB/NLBに別ポートでぶら下げる機能。複数サービスを同一のALB/NLBで扱えるようにするもの。今は単にあったらそれにattachしていて、ALB/NLBの設定を反映させてなかったりするので、その辺りどういう感じにするべきかもう一度考えてみようと思います。

参考など

*1:厳密にはデプロイされても平気な構成であるべきですが、全く必要なくコンテナの入れ替えが走るのは良くないので防ぐべきです。

Goでファイルの特定位置から読む

bashで組んでたログの検知スクリプトを、メンテナンス性と拡張性考えてGoで書き直すことにしました。 ファイルを定期的にtailして一定行数読み込んで処理する方式を取っていましたが、最後に読んだとこから後にしたいと調べていたら、Mackerelが似たようなことをやっていたので参考にしました。

go-check-plugins/check-log.go at master · mackerelio/go-check-plugins · GitHub

File.Seek()を使って、ファイルの特定バイトから読むようにできます。 前回読み込んだバイト数を記録しておけば、次はその後から追加されたデータのみを読むことができます。

whence*1が、ぱっと見何かわからなかったのですが、この値によってoffsetの使われ方が変わります。

whence 意味
0 ファイルの先頭からのoffset(先頭からスキップするバイト数)
1 今のSeek位置からのoffset(前回Seekした位置からスキップするバイト数)
2 ファイルの末尾からのoffset(末尾から読む。この場合は負数にしないと読めません)

サンプル

Readだけ動作試してみました。

一部抜粋

data

File seek in Go.

コード

fp, err := os.Open("./data")
if err != nil {
    panic(err)
}
defer fp.Close()

fp.Seek(5, 0)
b, err := ioutil.ReadAll(fp)
if err != nil {
    panic(err)
}
fmt.Printf("offset 5, whence 0: %s\n", string(b))

出力

offset 5, whence 0: seek in Go.

whence=2の時にoffsetをマイナスにしてなくて、読めないなーと勘違いしてました。今回試したのはReadですがWriteも同様に位置指定して書き込めるようです。その際は、os.Open()はReadOnlyなので、os.OpenFile(), os.Create()あたりで書き込める状態で作る必要があります。

*1:whenceあんまり聞かない単語でGoogleに投げると「そこから」と訳されました。どこから?の区分値と捉えると納得