年中アイス

いろいろつらつら

ローカルとAWS ECS上のマイクロサービスをSSH経由でつなぐmogura

マイクロサービス開発向けに、moguraというツールを開発しています。Twitterではちょいちょいリリースを呟いて、細かいバージョンアップを行っています。一旦落ち着いたので、moguraが何を解決するためのツールで、何をしてくれるのか紹介します。

背景と課題

マイクロサービス化を進めていて、複数のサービスがECS上で動作しています。それらをまとめて使うサービスを開発しようとした時に、全てローカル環境で構築するのは困難で、AWS上の開発環境に接続したくなります。RDSやelasticsearch service、他EC2で動かしているサービスは、Aレコード、CNAMEのドメインと特定のポートに対して、トンネルを作れば動いていたので、シェルでsshコマンドを実行するだけでも何とかなっていました。

例えば、以下のようにbastion.example.comサーバにSSH接続して、localhost:8080remote.example.com:80への通信をバインドするという形が取れます。

ssh bastion.example.com -L 8080:remote.example.com:80

しかし、ECSのサービスを起動すると各タスクは、クラスタ上のホストと自由なポート番号で動き、それがSRVレコードとして登録されます*1sshトンネルはAレコードやCNAMEは解決してくれますが、SRVレコードを解決してポート含めて対応をしてくれません。そのため、ECSにデプロイするたびに、自分でECSコンソールを見る or SRVレコードを引いて、結果をもとにsshトンネルコマンドを実行するという作業をする必要がありました。

課題一覧

  • ECSが作るSRVレコードが解決できない(これはSRVレコード自体が解析できないことと、VPC上でのみで解決可能なPrivateDNSの2つがあります)
  • デプロイのたびに変わる接続先を手動で解決する必要がある
  • それらに対するSSHトンネリングを作り直す必要がある(SSHコマンド再実行)

流石にそれは非効率すぎるので、少し前に書いた以下をベースにmoguraを実装しました。

reiki4040.hatenablog.com

修正やエラー制御等々を追加したので、その時からは実装がだいぶ変わっています。

moguraは何をしてくれるのか

moguraに以下を設定します。

  • 踏み台サーバ(bastion)へのSSHアクセス情報
  • 接続したいサービスのDNSとローカルで使うポート番号などを、必要なサービス分

まず、VPC環境にアクセスするための踏み台サーバ(bastion)へssh接続を行います。そこから、bastion上でDNSを解決し、繋ぎたいサービスの接続先とポートを取得してトンネルを作り、ローカルポートへの接続があったら、トンネルを通してデータを送受信しています。

moguraを使えば、設定に書いてある接続先がSRVレコードでも解決してくれるので、まず自分でSRVレコードを引く必要がありません。複数を設定に書いて一気に起動できるので、例えばマイクロサービスA,B,CとMySQLサーバ、elasticsearchサーバへの接続を一気に確立する(localポートにバインドする)といったことが可能です。また、定期的なDNSの再解決、接続が切れたら再接続をしてくれるので、スムーズにECS上の他のサービスやRDSなどとの接続を使うことができます。

細かい使い方についてはmoguraのREADMEをご覧ください。

今後の展望

とりあえず、面倒臭い部分の解決が実現でき、再接続などの使用中の細かい挙動については修正できました。ツールとしては、一定使える状態になっています。同僚などに使ってもらい、便利、楽といった感想をもらえているので満足です。

以下のような部分を実装すると面白そうだなと思いつつ、そんなにすぐ必要性を感じていないので、保留状態です。

  • SRVレコードの解決で、優先順位の判断やバランシングは行なっていないといった細かいもの
  • System Manager/Instance connectの機能で、IAMユーザでのSSH接続確立ができるようになったので、それを使ったbastionへの接続対応
  • fault injection? (通信断の意図的な発生)
  • ECSサービスを自動で検出する(今は設定に明示的に書く必要があるので、動的か、実行して設定ファイルを自動生成するか)
  • bastionサーバがEC2前提なので、それもFargateなどで動的に起動して使えるといった自動構成

System ManagerのSSHあたりは、mogura作って以降、(たまたま)急に拡充されだしました。

fault injectionは本当はenvoyなどのサイドカーで行うの良さそうですが、開発中のテスト用に簡易的なものはmoguraに入れといてもいいかなと思っています。 - envoy/examples/fault-injection at master · envoyproxy/envoy · GitHub

bastionサーバは、必要な時にFargateで起動して使えるとかだとEC2を管理する必要のなくなり、運用しやすそうなので、うまいこと作りたいです。

*1:ServiceDiscoveryを指定した場合

突然SIMが使えなくなったので再発行した(無事解決)

先日、急にSIMエラーというダイアログが表示された後、モバイル回線(4G LTE)が使えなくなりました。端末とSIMは以下の通り。

使えなくなるとiPhone左上のdocomoなどキャリア名が表示されているところが、不正なSIMですという表記に変わりました。

前兆として何回かSIMエラーというダイアログは出ていましたが、その時は問題なく使えていました。地下鉄だったので、電波の関係でエラーになったのかなと、深くは考えていませんでした。

使えなくなったのが土曜日で、外出から家に帰ってきていたので影響はありませんでしたが、外だとLINEなどで連絡が取れなかったり、Suica/ApplePay系のサービスが使えないかもで不便です。決済系は多用しているので、使えないのは厳しく、一番影響があるのが、会社行くのにそのまま電車に乗れない点。訂正:Suicaはオフラインでも使えました。

対処方法の調査

調べてみると、以下のものを試してみるように書かれていたので、全て試しましたがダメでした。

  • 機内モード切り替え
  • iPhoneの再起動
  • SIMの抜き差し
  • キャリアアップデート (特になかった)
  • iOSのアップデート(12.3.1 -> 12.4)

端末ロック(キャリアで買ってた場合、未払いや盗難等でのロックがあるらしい)に言及しているページもありましたが、SIMフリーで買ったので、この線はまずなし。加えて、手元に残っていたSIMフリーiPhone5sに刺してみても、同様に不正なSIMですと出るので、端末側の問題ではないことは確定しました。

再発行手続き

SIMがダメになってるようなので、再発行をOCNに問い合わせてみることに。マイページに行ってみるものの、紛失や再発行の申請は電話のみでした。

www.ntt.com

年末年始を除いて日曜もやってたので、翌日日曜に電話しました。サポート特有の混み合っています、という案内のしばらく後に繋がって、事情を説明すると、再発行してもらえることに。再発行からお届けまでは使えなくなる点だけご了承くださいとのことで*1、とても丁寧に対応してもらえました。あとは届くのを待つのみです。届いたら追記予定。

サポート電話終了後、自動音声のアンケート(解決しましたか?丁寧でしたか?とかのやつ)あったんですが、ゆっくりの自動音声なのはいいとして、声のトーンが夏のホラーな感じで、ややビビるので、OCNさんあれはちょっと変えたほうがいいんじゃないですかね。

可用性向上のために

もともと、電話回線と、iPhone用データ回線と、モバイルルータ回線を持ってるので、まず全部死ぬことはないんですが、iPhoneのSIMが使えないことで、Suicaが実質使えないのが厳しいことが判明しました。多分モバイルルータつないでたら使えると思いますが、そのタイミングだけ起動するとかめんどくさすぎる。訂正:Suicaはオフラインでも使えました。また、モバイルルータに刺さってるSIMは、MicroSIMでiPhoneのnanoSIMとはサイズが違い、直接刺せませんでした。

今回、データ通信nanoSIMをもう一つ買い増すかなと考えています。もともとandroidiPad用のSIM欲しいなーとは思ってたので、バックアップ物理SIMがてら、データシェア用のSIMを発行しようかと思います。

また、眠っていたPASMO(物理カード)を発掘し、残高をチャージした*2ので、SIMが届くまではこれで通勤しようかと思います。店舗支払いと異なり、公共交通乗る時のSuicaだけは、無いと不便すぎるので、物理カードを念のために持っておいても良さそうです。

追記 SIM到着し復旧完了

火曜日にはSIMが届き、装着するとdocomoの表記が出て無事に4G回線を使うことができました。日曜の昼過ぎにサポートに問い合わせたのを考えると、割と早く復旧することができました。

また、iPhoneSuicaはオフライン(機内モード)でも、使うことができましたので訂正。

参考

*1:使えてないので、再発行関係ないけれども

*2:駅じゃなくても、セブンATMでチャージできるの知らなかった

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が複数ある場合のバランシング(これはできるんだろうか?)
  • 多分色々エラーハンドリング

参考