この記事は、Kong - API Gateway Pattern速習会@Wantedlyの資料として作られたものです。
サービスが大きくなるとやりたくなってくること
- より高速な実装に置き換えたい
- APIを複数のサービスに分けて開発したい
- マイクロサービス化
何故マイクロサービスか
- James Lewis/Martin Fowlerの"Microservices"日本語訳
- State of the Art in Microservices by Adrian Cockcroft - Qiita
いろいろ理由は言われてるけど...
- 人が扱える大きさの限界
- 「明確な」境界が必要
- 名前空間やスコープなど、プログラミング言語でも使用している
- 一段上の概念だと思うと良い
- 大きいメソッドが管理できないのと同様に、大きいサービスも管理できない
- 組織体系に影響を与える
- 100人のチームで開発するのが嫌ならやった方が良い
- 数人のチーム * 20個とかでもコンフリクト無く進められる
ここで行うこと
どうやって複数のAPIを
- 連携するか
- 管理するか
- 存在するAPIを見失わないか
- 使っていいAPIだけ使えるか
- 変更するか
- 特に内部の実装言語の変更するか
という問題を解決できるソフトウェアの使い方を学び、ある程度触ってみる。
(ついでにDocker Composeとかにも詳しくなる)
API Gateway Pattern
「見た目はモノリシック、実装はマイクロサービス」
- 一箇所見に行けば全てのAPIを見つけられる
- 細かい権限管理も可能
- 各APIで何回も実装しないといけない部分を省略できる
- Authentication
- Rate Limiting
実際の例
どう実現するか
Kongを使いましょう。
公式サイトより。左がLegacy、右がKongを使った場合。
他の方法
自分の知る限り、実際いいのが他にないです。
あり得る方法:
- Nginxで自前実装
- KongはOAuthまで実装しているのでそれを自前でやるのは大変そう
- AWS API Gateway
- 下におけるものがLambda等なので、マイクロを通り越してナノサービスな気がして実際使いにくそう
気をつけなければならないこと
- Gatewayが死んだら全てのAPIが終わり
- 絶対に(99.99...%)死なないようにする
- 早急な復旧手順を作っておく
Kongの場合
- データストアに高スケーラビリティで有名なCassandraを使用
- 逆にちょっと「うっ」って思う部分でもあるけど、Dockerですぐ立ち上がる
- 別のデータストアも使えるようになる計画はある模様
- CassandraをS3にバックアップとるものは多数存在
- Kong自体は死んでもcassandraが生きていれば元通りになる
- Kongはクラスタを組める作りになっているのでスケーラビリティもある
Kongを使ってみる
インストール
Dockerを使って行います。
以下の2通りのどちらでもいいです。
- https://getkong.org/install/docker/ に従うだけ
- Docker Composeで立ち上げる
1. ドキュメントの通りにやる
$ docker pull cassandra:2.2.5
$ docker pull mashape/kong
$ docker run -p 9042:9042 -d --name cassandra cassandra:2.2.5
ここでちょっと待つ。(cassandraがきちんとreadyになってからでないと次が失敗する)
$ docker run -d --name kong \
--link cassandra:cassandra \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 7946:7946 \
-p 7946:7946/udp \
--security-opt seccomp:unconfined \
mashape/kong
動いているか確認
$ curl http://$(docker-machine ip default):8001
2. Docker Composeで立ち上げる
作業用ディレクトリを作り、docker-compose.yml
を以下のように書きます。
version: '2'
services:
kong:
image: mashape/kong
depends_on:
- cassandra
ports:
- 8000:8000
- 8443:8443
- 8001:8001
- 7946:7946
- 7946:7946/udp
security_opt:
- seccomp:unconfined
cassandra:
image: cassandra:2.2.5
ports:
- 9042:9042
このあと
docker-compose up
するだけです。
ただ以下に書くWaitに関する悲しい現実があるので、1回目はkongの立ち上げが失敗すると思います。その場合、
- 2回
docker-compose up
- 以下のように別々で、少し間を空けてコマンドを叩く
$ docker-compose up cassandra
$ docker-compose up kong
をするとよいです。
悲しいことにdepends_on
系の書き方全て、きちんと立ち上がるまで待ってくれない
Compose always starts containers in dependency order....
However, Compose will not wait until a container is “ready”
https://docs.docker.com/compose/startup-order/
- 荒れるIssue (244 comments)
- https://github.com/docker/compose/issues/374
3. できあいのものを使う
Kongだけじゃなく、goやrailsで実装したサンプルJSON APIを含んだものを用意しました。
git clone git@github.com:awakia/modern-architecture-2016.git
docker-compose up cassandra
少し待って、
docker-compose up --no-deps kong
時間があるようだったら、
docker-compose build
しておいてください。
既に立っているコンテナの止め方
docker rm -f <container-name>
で立ち上がっているコンテナを止めて削除することが出来ます。
docker rm -f $(docker ps -a -q)
で、全てのコンテナを消すことが出来ます。
ちなみに、
docker rm $(docker ps -a --filter 'status=exited' -q)
で、全ての終了しているコンテナを消すことが出来ます。
APIの登録
関連公式ドキュメント:
- チュートリアル: https://getkong.org/docs/0.7.x/getting-started/adding-your-api/
- リファレンス: https://getkong.org/docs/0.7.x/admin-api/
- プロキシの仕組み解説: https://getkong.org/docs/0.7.x/proxy/
まず知っておくと良いこと
-
8001
番がAPIのAdmin用 (admin_api_listen) -
8000
番がHTTP経由のAPIのユーザー用 (proxy_listen) -
8443
番がHTTPS経由のAPIのユーザー用 (proxy_listen_ssl)
なので管理系のリクエストは基本的に以下の形になります。
curl -X POST http://$(docker-machine ip default):8001/apis/ --data 'hoge=fuga'
APIの準備
先ほどの
git clone git@github.com:awakia/modern-architecture-2016.git
を使い、
docker-compose up go-api
しましょう。
中味は go-api/server.go とそれを立ち上げるDockerfileなので、多少目を通しておきましょう。
APIの追加
$ curl -i -X POST \
% --url http://$(docker-machine ip default):8001/apis/ \
% --data 'name=go-api' \
% --data 'upstream_url=http://go-api:5000' \
% --data 'request_path=/go'
HTTP/1.1 201 Created
Date: Wed, 30 Mar 2016 19:40:37 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","created_at":1459366837000,"name":"go-api"}
コピー用
curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=go-api' \
--data 'upstream_url=http://go-api:5000' \
--data 'request_path=/go'
登録されているAPIの確認
コマンドライン上で確認
$ curl http://$(docker-machine ip default):8001/apis | jq
ブラウザで確認
$ open http://$(docker-machine ip default):8001/apis
- ブラウザ上で整形するなら以下がオススメ
実際に動いているか確認
open http://$(docker-machine ip default):8000/go
追加したAPIの修正
$ curl -i -X PATCH \
% --url http://$(docker-machine ip default):8001/apis/go-api \
% --data 'strip_request_path=true'
HTTP/1.1 200 OK
Date: Wed, 30 Mar 2016 19:55:45 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","name":"go-api","strip_request_path":true,"created_at":1459366837000}
コピー用
curl -i -X PATCH \
--url http://$(docker-machine ip default):8001/apis/go-api \
--data 'strip_request_path=true'
何が変わったか確認
open http://$(docker-machine ip default):8000/go
/go => /
としてupstreamのAPIに送られるようになっている!
プラグインの利用
https://getkong.org/plugins/ にあるように様々なプラグインがあります。
現状、以下の6種類にわけられています。
- Authentication
- Security
- Traffic Control
- Analytics & Monitoring
- Transformations
- Logging
また、プラグインは自分で作ることもできます。
OAuthプラグインの利用
これが、この中で一番使うのが難しいプラグインだと思います。
なお、公式チュートリアルではもう少し簡単なAuthenticationの例を使っているのでこれがToo muchな人はそちらを参考にしてください
- https://getkong.org/docs/0.7.x/getting-started/enabling-plugins/
- https://getkong.org/docs/0.7.x/getting-started/adding-consumers/
OAuthプラグインの追加
$ curl -X POST http://$(docker-machine ip default):8001/apis/go-api/plugins \
% --data "name=oauth2" \
% --data "config.enable_client_credentials=true"
{"api_id":"93148ea0-0776-4f18-a426-605418158c17","id":"56971111-d189-4377-811f-deee02f1374d","created_at":1459371154000,"enabled":true,"name":"oauth2","config":{"mandatory_scope":false,"token_expiration":7200,"enable_implicit_grant":false,"hide_credentials":false,"provision_key":"21eaa43d19934b60a7198ab463040af0","accept_http_if_already_terminated":false,"enable_authorization_code":true,"enable_client_credentials":true,"enable_password_grant":false}}
コピー用
curl -X POST http://$(docker-machine ip default):8001/apis/go-api/plugins \
--data "name=oauth2" \
--data "config.enable_client_credentials=true"
- config.enable_client_credentials
というオプションをtrueにしています。
これはOAuthの4つのGrantフローのうち最も簡単な"Client Credentials Grant"というフローを有効にしています。
ページの確認
open http://$(docker-machine ip default):8000/go
access_tokenが提供されていないので、error: invalid_request
になっていればうまく動いています。
OAuthの4つのフロー
OAuthでは、Access Tokenを得るためにどう認証するかが大きく4つに分かれています。
フローはいろいろありますが、結局はAccess Tokenを得て、それと一緒にAPIリクエストをしたらきちんと結果が帰ってくることだけわかっていれば大丈夫です。
名前 | どんな時に使うか |
---|---|
Authorization Code Grant | RailsなどバックエンドのサーバーサイドでOAuthする時 |
Implicit Grant | JavascriptなどWebブラウザのクライアントサイドでOAuthする時 |
Resource Owner Password Credentials Grant | PCやモバイルアプリなどで、他の方法が使えない環境でOAuthする時 |
Client Credentials Grant | 社内のAPIサーバー等信頼できるクライアントからOAuthする時 |
Kongのデフォルトでは、1番上のWebバックエンドサーバーでの認証でよく使われている"Authorization Code Grant"フローだけONになっています。
ただ、内部公開用APIだと、このフローはUIまで作らないといけなくめんどうなので、ここでは"Client Credentials Grant"フローを使います。
Client Credentials Grant で Access Token を取得
以下のフローで行います。
- まずAPIを使うサービス(または人)を表す
Consumer
を作成する
- 例えば、Facebook APIをWantedlyが使う時、Wantedly自体、もしくはWantedlyを作っている開発者がConsumer
- ConsumerがApplicationを作る
- 例えば、wantedly.comやSyncがApplication
- Access Tokenを取得
1. Consumerの作成
example_consumer
というコンシューマを作る場合、以下のようにします。
$ curl -X POST http://$(docker-machine ip default):8001/consumers/ --data username=example_consumer
{"username":"example_consumer","created_at":1459404233000,"id":"b5b221ec-12b7-45be-9830-3a84a97fbb6e"}
2. Applicationの登録
example_consumerというコンシューマが、サイト https://example.com 内の"Example App"というものを作ろうとした場合以下のようになります。
$ curl -X POST http://$(docker-machine ip default):8001/consumers/example_consumer/oauth2 --data name=Example%20Application --data redirect_uri=https://example.com/oauth2_callback
{"consumer_id":"b5b221ec-12b7-45be-9830-3a84a97fbb6e","client_id":"da5da65dca1044a2aac7d86a694b9536","id":"fdac9287-0c4b-4ffd-89a0-8ad711b6f758","name":"Example App","created_at":1459404289000,"redirect_uri":"https:\/\/example.com\/oauth2_callback","client_secret":"8df2d9f629ee4d049292614e1ee0524f"}
Application登録の際の必須項目は以下の2つです。
- name
- アプリケーションの名前 (例: Wantedly)
- redirect_uri
- リダイレクト先のURI (例: https://www.wantedly.com/oauth_callback )
これにより、以下のものが手に入ります。
- client_id
- client_secret
ちなみに、この2つの値をパラメータで与えて直接指定してしまうこともできます。
3. Access Tokenの取得
先ほど取得した、client_id、client_secretを元にリクエストを行います。
リクエストの際には、この2つの値の他に、Grantの方法を表すgrant_type=client_credentials
をつけなければなりません。
$ curl -k -X POST https://192.168.99.100:8443/oauth2/token \
% --data grant_type=client_credentials \
% --data client_id=da5da65dca1044a2aac7d86a694b9536 \
% --data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"token_type":"bearer","access_token":"4f739dc516a74360bcd00d47fc517955","expires_in":7200}
コピー用
curl -k -X POST https://192.168.99.100:8443/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
見事Access Tokenが取れていることがわかります。
これを元にアクセスをすれば、
とこれまでどおりアクセス出来るようになっていることがわかります。
Access Token取得のハマリポイント
実は、この3個めの段階はいくつかハマリポイントがあります。
- 使用するAPIのRequestHostを指定しておかなければならない
- HTTPSでPOSTアクセスしなければならない
実は上のリクエストではこの2つの問題を先に解決した結果を載せています。
これから、その2つのステップを解説します。
3.1. APIのRequest Hostを指定
何も考えずにリクエストすると、
$ curl -X POST http://$(docker-machine ip default):8000/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"request_path":"\/oauth2\/token","message":"API not found with these values","request_host":["192.168.99.100"]}
エラーとしてはそんなAPIは、そのリクエストホストでは存在しないよという感じになっています。
これは、先ほど作成したgo-api
のRequest Hostを指定していないためです。
3.1.1. Request Hostの追加方法1
request_hostとしてdocker-machine ip default
の値、192.168.99.100
を指定してあげると解決できます。
$ curl -X PATCH --url http://$(docker-machine ip default):8001/apis/go-api --data request_host=$(docker-machine ip default)
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","name":"go-api","strip_request_path":true,"created_at":1459366837000,"request_host":"192.168.99.100"}
3.1.2. Request Hostの追加方法2
適当にRequest Hostが192.168.99.100
のOAuthつきAPIを登録してしまいましょう。
$ curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=for_host' \
--data 'upstream_url=http://go-api:5000' \
--data request_host=$(docker-machine ip default)
HTTP/1.1 201 Created
Date: Thu, 31 Mar 2016 09:58:50 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","id":"800b9b40-4a23-43db-b9ab-e1d214f6c7c3","name":"for_host","created_at":1459418330000,"request_host":"192.168.99.100"}
$ curl -X PUT http://$(docker-machine ip default):8001/apis/for_host/plugins \
--data "name=oauth2" \
--data "config.enable_client_credentials=true" \
--data "config.token_expiration=0"
{"api_id":"800b9b40-4a23-43db-b9ab-e1d214f6c7c3","id":"67d4d8d2-f1fb-4f25-9a0b-ab106cfd4191","created_at":1459418404000,"enabled":true,"name":"oauth2","config":{"mandatory_scope":false,"token_expiration":0,"enable_implicit_grant":false,"hide_credentials":false,"provision_key":"cda2051a3f6b43d1aa079ca6b7d078a5","accept_http_if_already_terminated":false,"enable_authorization_code":true,"enable_client_credentials":true,"enable_password_grant":false}}
気持ち悪いけれど、実はこちらがオススメです。
実はRequestHostは他のAPI含めユニークに指定しないといけません。なので、複数のrequest_pathを使う実装が出来なくなてしまいます。
確認
この上で、先ほどと同じコマンドをもう一度打ってみましょう。
curl -X POST http://$(docker-machine ip default):8000/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"error_description":"You must use HTTPS","error":"access_denied"}
HTTPSでアクセスしろと言われていますが、一つ進みましたね。
3.2. HTTPSでのアクセス
HTTPS用のポートは8443
なので、そこのアクセスしてあげれば良いはずです。
ただ普通にリクエストしただけでは、SSLのcertificateの警告がでて、curl: (60) SSL certificate problem: Invalid certificate chain
といったエラーになってしまいます。
実際に使う際は、SSL Pluginをつかて、Certificateを入れたらいいですが、今回はローカルで実験するだけなのでこの警告を無視しましょう。
curlだと-k
オプションをつけるとセキュリティの警告を無視できます。
$ curl -k -X POST https://$(docker-machine ip default):8443/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"token_type":"bearer","access_token":"8d32e180038647e388d8eee748b82908","expires_in":7200}
これで新しいAccess Tokenが得られる状態になりました。
OAuth Pluginの削除
OAuthがあると今のうちはいちいちAccess Tokenを入れないとアクセスできなくなるので、消してしまいましょう。
PluginにはIDがあります。それを取得しましょう
$ curl http://$(docker-machine ip default):8001/apis/go-api/plugins | jq '.data[0].id'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 476 0 476 0 0 34472 0 --:--:-- --:--:-- --:--:-- 36615
"ce0d7078-31ea-4475-ad92-7fd567a4a106"
これをコピーして、DELETEクエリを発行しましょう。
curl -X DELETE http://192.168.99.100:8001/plugins/ce0d7078-31ea-4475-ad92-7fd567a4a106
おまけ: こんなことがしたい
- Expireされると困るので、Expireしないようにしたい。
- 追加したAPIのConfigurationで、
config.token_expiration=0
と設定すれば出来ます。
- コンシューマごとに使えるAPIの権限管理をしたい
- ACL Pluginを使えば出来ます。
- 具体的には、ConsumerにGroupを付与し、各APIはそのGroupをホワイトリストに入れるかブラックリストに入れるかのどちらかを選べます。
- OAuthで、scopeを使いたい
-
config.scopes
の[configuration]で指定できます。 - Authorizeの時は
scope
パラメータに渡します。 - API側では、
X-Authenticated-Scope
に認証済みのものがカンマ区切りで渡されるのでそれを使います。
参考URL
- 公式ドキュメント: https://getkong.org/plugins/oauth2-authentication/#client-credentials
- ソースコード: https://github.com/Mashape/kong/blob/master/kong/plugins/oauth2/access.lua
- 質問しているIssue: https://github.com/Mashape/kong/issues/1081
別の実装も立ち上げてみよう
Railsのサーバーも立ち上げてみよう
docker-compose up rails-api
をしてrails-apiが立っていることを確認
$ curl -i -X POST \
% --url http://$(docker-machine ip default):8001/apis/ \
% --data 'name=rails' \
% --data 'upstream_url=http://rails-api:3000' \
% --data 'request_path=/rails' \
% --data 'strip_request_path=true'
HTTP/1.1 201 Created
Date: Thu, 31 Mar 2016 09:47:12 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/rails-api:3000","request_path":"\/rails","id":"74c79c94-d723-4434-aaae-27af812c0eed","created_at":1459417632000,"strip_request_path":true,"name":"rails"}
コピー用
curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=rails-api' \
--data 'upstream_url=http://rails-api:3000' \
--data 'request_path=/rails' \
--data 'strip_request_path=true'
立っているか確認
open http://$(docker-machine ip default):8000/rails
これだけですが、RailsでもGoでも同じHost内で両方の実装が立ち上がることが確かめられました。
それぞれの言語で、同じ実装をして移行をするか、役割分担をする構造にするかはアナタ次第です。
まとめ
最初あった課題
どうやって複数のAPIを
- 連携するか
- 管理するか
- 存在するAPIを見失わないか
- 使っていいAPIだけ使えるか
- 変更するか
- 特に内部の実装言語の変更するか
どうやって複数のAPIを連携するか
- それぞれのマイクロサービスでAPIを作っていき、request_pathにより分岐させる
- Rate Limitingにより、内部APIでも他に高負荷をかけすぎる実装を排除
どうやって複数のAPIを管理するか
- 存在するAPIを見失わないか
open http://$(docker-machine ip default):8001/apis
- もちろんこれを元にUIを作っても良い
- 使っていいAPIだけ使えるか
- ACL Pluginを使って権限管理
- 内部からの呼び出しも、OAuthのアクセストークンを使う
どうやってAPIの内部の実装言語の変更するか
request_pathにより、それぞれのAPIの実装を分けておき徐々に移行していく。つまり、
- /api/user, /api/company, /api/job をれぞれ最初は同じUpstreamURL
- /api/user だけ別の実装をし、UpstreamURLを変える
- これを続けていって移行完了
トラブルシューティング
ネットワークの問題でImageがPull出来ない
注: 本当にネットワークにつながってないなんてことがないかは確認してください。
エラーメッセージ
Network timed out while trying to connect to https://index.docker.io/v1/repositories/usename/reponame/images. You may want to check your internet connection or if you are behind a proxy.
対処法
virtual machineの問題みたいです。再起動させると直ります。
docker-machine restart default
サービスが動いているはずのURLにアクセスしてもつながらない
対処法
まず、docker-machineを使っている場合は、localhost
, 127.0.0.1
ではなく
docker-machine ip default
で出てくるIPアドレス(通常192.168.99.100
)に繋ぐ必要があるので、そこが正しいか確認します。
正しいIPで正しいPortに接続している場合、とりあえず、
docker ps
の出力を見て、ちゃんと動いているか見てみましょう。
何らかの場合で死んでいたり、portが正しくexposeされていないなどがわかったりします。
死んでいる場合、そもそも表示されていないはずなので、
docker ps -a
でexitedになっているもののnameを見つけ
docker logs <container-name>
で、何故死んだかを把握すると良いです。
動いている場合、
docker logs <container-name>
でエラーが吐かれていないかログを見たり、
docker exec -it <container-name> bash
など(imageによって、bash
をbin/sh
にする必要があったりします)で、中に入って、
- fileに吐かれているログを見てみる
- 実際に起動コマンドやコンソールコマンドを叩いてみる
- ネットワークの状況(
ifconfig
,ip addr
など)を確認してみる
などをして原因を探ります。
rails-apiコンテナでDBが作成されていない、migrateされていない
対処法
docker上でコマンドを走らせるにはdocker run
もしくは、docker-compose run
を使います。
今回の場合、以下のコマンドで動くはずです。
docker-compose run rails-api rake db:create db:migrate
もちろん何らかの理由でDBに接続できていないと出来ないので、その場合は、
- きちんとDBコンテナが立っているか
-
docker ps
で確認し、docker-compose up postgres
で起動 - ネットワークは
postgres
というホスト名で引きに行くことが出来るか -
docker exec -it <rails-container-name> bash
で中にはいり、ping postgres
が通るか調べる
をすると良いです。
kongのコンテナが立っていない
対処法
cassandraのコンテナが立ち上がって、readyになっていない状態でkongを立ち上げようとすると死んでしまいます。
ちょっと待ってから立ち上げたらいいだけなので、もう一度立ち上げてみる(docker-compose up kong
など)とすんなり動くはずです。