@ijin

[Michael H. Oshita]

Elastic Beanstalkへの簡単ssh

AWS Elastic Beanstalk上で管理されているinstanceにsshするにはeb sshコマンドを使えば割りと簡単に接続できますが、アクセスキーの設定が必要で、開発者が多い場合にそれを発行してばら撒いて管理するのはかなり面倒です(IAM user/roleの割当にしても)。特に極稀にしか接続する必要がない場合。

そこで、API GatewayとLambdaでinstanceのpublic ipを返却するエンドポイントを作り、SSH configを設定すれば誰でもアクセスできるようにしてみました。

前提条件

  • instanceにはログインユーザーのssh keyが登録積み(.ebextensions等で)
  • ssh localhost可能である
  • Elastic Beanstalkのenvironment名は数値で終わらない
  • Auto ScalingのTermination PolicyがNewestInstance

Lambda

やってる事は単純で自動的に付与されるelasticbeanstalk:environment-nameタグのついたinstanceのipを(パラメータに応じて)返却するだけ。複数ある場合は、起動した順で。

せっかくなので新しくサポートされたPythonで記述。

当然、LambdaのIAM roleでec2:Describe*のAction許可も必要。

Swagger

API Gatewayのresourceをコンソールでポチポチ作るのがイヤだったのでAPIの状態をSwaggerで定義してみた。awscliは生REST APIが辛い

編集しながらドキュメントも見れるonline editorが結構便利。

API Gateway

Swagger上でYAMLで作成した定義書はjsonとしてexportし、aws-apigateway-importerでAPIのresourceやmethodやらを生成する。

import処理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ./aws-api-import.sh -c swagger.json
2015-11-04 02:23:18,507 INFO - Attempting to create API from Swagger definition. Swagger file: swagger.json
reading from swagger.json
2015-11-04 02:23:18,649 INFO - Parsed Swagger with 2 paths
2015-11-04 02:23:18,655 INFO - Creating API with name EB API
2015-11-04 02:23:19,417 INFO - Removing default model Error
2015-11-04 02:23:19,634 INFO - Removing default model Empty
2015-11-04 02:23:19,872 INFO - Creating model for api id nx3r6d6yhh with name IP
2015-11-04 02:23:20,114 INFO - Generated json-schema for model IP: {"type":"object","properties":{"ip":{"type":"string","description":"IP address."}},"definitions":{}}
2015-11-04 02:23:20,329 INFO - Creating model for api id nx3r6d6yhh with name Error
2015-11-04 02:23:20,338 INFO - Generated json-schema for model Error: {"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"},"fields":{"type":"string"}},"definitions":{}}
2015-11-04 02:23:20,770 INFO - Creating resource 'eb' on bd5pqsq37h
2015-11-04 02:23:21,420 INFO - Creating resource '{env_name}' on 8rjsbt
2015-11-04 02:23:22,079 INFO - Creating resource 'ip' on 4cg26s
2015-11-04 02:23:22,931 INFO - Creating method response for api nx3r6d6yhh and method GET and status 200
2015-11-04 02:23:23,144 INFO - Creating new model referenced from response: IPaddresses
2015-11-04 02:23:23,144 INFO - Creating model for api id nx3r6d6yhh with name IPaddresses
2015-11-04 02:23:23,153 INFO - Generated json-schema for model IPaddresses: {"type":"string","definitions":{}}
2015-11-04 02:23:23,770 WARN - Default response not supported, skipping
2015-11-04 02:23:23,772 INFO - Creating method parameter for api nx3r6d6yhh and method GET with name method.request.path.env_name
2015-11-04 02:23:23,998 INFO - Creating method for api id nx3r6d6yhh and resource id md6qcm with method get
2015-11-04 02:23:24,822 INFO - Creating resource '{server_num}' on qmd6mc
2015-11-04 02:23:25,689 INFO - Creating method response for api nx3r6d6yhh and method GET and status 200
2015-11-04 02:23:25,896 INFO - Found reference to existing model IPaddresses
2015-11-04 02:23:26,306 WARN - Default response not supported, skipping
2015-11-04 02:23:26,306 INFO - Creating method parameter for api nx3r6d6yhh and method GET with name method.request.path.env_name
2015-11-04 02:23:26,525 INFO - Creating method parameter for api nx3r6d6yhh and method GET with name method.request.path.server_number
2015-11-04 02:23:26,764 INFO - Creating method for api id nx3r6d6yhh and resource id 5udxeo with method get

これでAPI Gateway上に階層が出来上がり。

後は各GET methodでLambdaへの関連付けをし、Integration RequestIntegration Responsemapping templateでパラメータやレスポンスを設定。(多分、この手順もswaggerで定義できるとは思うけど)

integration request

integration response

参考:

endpoint

Deployすれば、以下のAPIでipが取得可能。

/$API_ENDPOINT/prod/eb/$EB_ENV_NAME/ip/[n]

instance ip一覧

1
2
3
$ curl -s https://altvsxa2kj.execute-api.ap-northeast-1.amazonaws.com/prod/eb/my-ebenv-production/ip
54.189.149.5
54.189.168.83

特定のinstance ip

1
2
$ curl -s https://altvsxa2kj.execute-api.ap-northeast-1.amazonaws.com/prod/eb/my-ebenv-production/ip/1
54.189.149.5

ipの部分だけ、PATHじゃなくquery paramterにした方がAPI Gatewayの設定がシンプルだったけど、少しでもRESTfulにしたかったもので。(まあ、そもそもjson返してないけど)

SSH Config

さて、ここからがキモです。

~/.ssh/config

1
2
3
4
Host my-ebenv-production*
  User ec2-user
  StrictHostKeyChecking no
  ProxyCommand ssh -A localhost -W `N=$(grep -o '[0-9]\+$' <<< %h || echo 1); E=$(perl -pe 's/\d+$//' <<< %h); curl -s https://altvsxa2kj.execute-api.ap-northeast-1.amazonaws.com/prod/eb/$E/ip/$N`:%p

上記の設定でssh $EB_ENV_NAME[n]で指定したサーバに接続できるようになります。

仕組みとしては、ホスト名をparseしAPI Gatewayに適切なリクエストを行い、返却されるpublic ipにローカル端末自身を踏み台としてsshするProxyCommandの設定です。

例えば、2番目(に起動した)のサーバに接続する場合は

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ssh my-ebenv-production2
Last login: Wed Nov  4 01:19:14 2015 from ndx6-ppp413.tokyo.sannet.ne.jp
 _____ _           _   _      ____                       _        _ _
| ____| | __ _ ___| |_(_) ___| __ )  ___  __ _ _ __  ___| |_ __ _| | | __
|  _| | |/ _` / __| __| |/ __|  _ \ / _ \/ _` | '_ \/ __| __/ _` | | |/ /
| |___| | (_| \__ \ |_| | (__| |_) |  __/ (_| | | | \__ \ || (_| | |   <
|_____|_|\__,_|___/\__|_|\___|____/ \___|\__,_|_| |_|___/\__\__,_|_|_|\_\
                                       Amazon Linux AMI

This EC2 instance is managed by AWS Elastic Beanstalk. Changes made via SSH 
WILL BE LOST if the instance is replaced by auto-scaling. For more information 
on customizing your Elastic Beanstalk environment, see our documentation here: 
http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html

1番目の場合は明示的に指定する必要はありません。

1
$ ssh my-ebenv-production

また、Auto Scalingでinstanceが増減せずに1台のみの場合はもっとシンプルに記述できます。

1
  ProxyCommand ssh -A localhost -W `curl -s https://altvsxa2kj.execute-api.ap-northeast-1.amazonaws.com/prod/eb/$E/ip`:%p

簡単!

でも、よくよく考えたら普通のAuto Scalingの場合でも利用できる事に気付きました。

まとめ

  • Swaggerなかなか面白い
  • API GatewayとLambda便利
  • Lambda pythonも良い。けど、rubyサポートも早う。。

Comments