- 協調行動
- 社会的グルーミング
- 性選択(性淘汰)
- 内集団バイアス
- ペドフィリア検出
参考
user(id: "1") { name friends { name }}
{ "user": { "name": "User One", "friends": [ { "name": "Friend One" }, { "name": "Friend Two" }] }}
{ me { name age }}
{ "me": { "name": "Toshihiro Suzuki", "age": 31 }}
{ user(id: 3) { name age }}
{ me { name age profilePicture { width height url } }}
{ "me": { "name": "Toshihiro Suzuki", "age": 31, "profilePicture": { "width": 50, "height": 50, "url": "https://abc.com/abc.jpg" } }}
{ me { name age profilePicture(size: 300) { width height url } }}
{ "me": { "name": "Toshihiro Suzuki", "age": 31, "profilePicture": { "width": 300, "height": 300, "url": "https://abc.com/300.jpg" } }}
{ me { name age lilPic: profilePicture(size: 50) { width height url } bigPic: profilePicture(size: 300) { width height url } }}
{ "me": { "name": "Toshihiro Suzuki", "age": 31, "lilPic": { "width": 50, "height": 50, "url": "https://abc.com/50.jpg" }, "bigPic": { "width": 300, "height": 300, "url": "https://abc.com/300.jpg" } }}
{ me { name age friends { name age } }}
{ "me": { "name": "Toshihiro Suzuki", "age": 31, "friends": [ { "name": "Ryota Nishio", "age": 30, }, { "name": "Ichiro Fukuda", "age": 31, }, { "name": "Masahiro Yasuda", "age": 30, } ] }}
このように、SQLではJoinを使う必要があるので多少複雑になってしまうクエリも、GraphQLでは比較的直感的に書くことができます。{ me { name age friends { name age } }}
fragment userFragment on User { name age}{ me { ...userFragment friends { ...userFragment } }}
type Query { me: User user(id: Int): User}
type User { name: String age: Int profilePicture(size: Int = 50): ProfilePicture friends: [User]}
type ProfilePicture { width: Int height: Int url: String}
{ me { name age profilePicture { width height url } friends { name age } }}
$ npm install graphql
type Query { me: User user(id: Int): User}
type User { name: String friends: [User]}
{ me { name }}
{ me { name friends { name } }}
{ user(id: "2") { name friends { name } }}
その結果として以下のJsonが返ってきます。{ "me": "Toshihiro Suzuki"}
{ "me": "Toshihiro Suzuki", "friends": [ { "name": "Ichiro Fukuda" }, { "name": "Ryota Nishio" } ]}
{ "user": "Ichiro Fukuda", "friends": [ { "name": "Toshihiro Suzuki" } ]}
AWAサーバサイドエンジニアの辻(jun06t)です。
今回はiOSとAndroidの月額課金のための実装について書かせていただきます。
形式として読み物と言うよりドキュメントっぽくなっています。
理由は私が実装しようとした際に実装方法についてまとめて書かれた記事が少なく、「検証時に使えるフィールドはどれだろう?」「昔はこうだったけど、今は違う?」「Androidではできるけど、iOSではできない(逆も然り)」など、色々と分からない部分が多くとても困ったためです。
やや長い記事となったため、iOSの実装を前編、Androidの実装を後編として説明させていただきます。
※1:開発中にプラットフォーム側の仕様変更があったなど、記載している内容は情報が古い可能性があります。
※2:記載している動作は十分に調査できていないものも含んでいるため、内容が不正確である可能性があることをご了承ください。
おおまかに以下の流れになります。
左半分はクライアントサイドで閉じており、右半分はサーバサイドで閉じてます。
以降はサーバ - AppStoreAPI間の検証処理について述べていきます。
AppStoreAPIのエンドポイントにはSandbox用と本番用があります。
それぞれ使うレシートが違うため、間違った方に送信すると後述するエラーコード21007
や21008
が返ります。
Appleの審査中はSandboxレシートを使用するため、本番サーバでは両方共扱える実装になっている必要があります。
例)必ず最初本番にレシートを投げ、21007
が返ってきたらSandboxのエンドポイントへ投げ直す。
上記エンドポイントに対して以下のbodyでリクエストします。
{ "receipt-data": "MIIu0QYJKoZIhvcNAQcCoIIuwjCCLr4CAQExCzAJBgUrDgMCGgUAMIIeggYJKoZIhvcNA...", "password": "hogehoge3ddad5a2f2c06fe47fcdf22e"}
正しいレシートだと以下の結果が返ってきます。
※iOS7以降のフォーマットです。transaction_id等はダミーです。
{ "status": 0, "environment": "Sandbox", "receipt": { "receipt_type": "ProductionSandbox", "adam_id": 0, "app_item_id": 0, "bundle_id": "your_bundle_id", "application_version": "1", "download_id": 0, "original_application_version": "1.0", "in_app": [ { "quantity": "1", "product_id": "your_product_id", "transaction_id": "900200162803342", "original_transaction_id": "4002002162812828", "is_trial_period": "false", "app_item_id": "", "version_external_identifier": "", "web_order_line_item_id": "3300000022116144", "purchase_date": "2015-07-09 08:19:17 Etc/GMT", "purchase_date_ms": "1436429957000", "purchase_date_pst": "2015-07-09 01:19:17 America/Los_Angeles", "original_purchase_date": "2015-07-09 08:17:27 Etc/GMT", "original_purchase_date_ms": "1436429847000", "original_purchase_date_pst": "2015-07-09 01:17:27 America/Los_Angeles", "expires_date": "2015-07-09 08:24:17 Etc/GMT", "expires_date_ms": "1436430257000", "expires_date_pst": "2015-07-09 01:24:17 America/Los_Angeles", "cancellation_date": "", "cancellation_date_ms": "", "cancellation_date_pst": "" }, { "quantity": "1", "product_id": "your_product_id", "transaction_id": "4500000152802828", "original_transaction_id": "4002002162812828", "is_trial_period": "false", "app_item_id": "", "version_external_identifier": "", "web_order_line_item_id": "3300000022116145", "purchase_date": "2015-07-09 08:14:17 Etc/GMT", "purchase_date_ms": "1436429657000", "purchase_date_pst": "2015-07-09 01:14:17 America/Los_Angeles", "original_purchase_date": "2015-07-09 08:14:18 Etc/GMT", "original_purchase_date_ms": "1436429658000", "original_purchase_date_pst": "2015-07-09 01:14:18 America/Los_Angeles", "expires_date": "2015-07-09 08:19:17 Etc/GMT", "expires_date_ms": "1436429957000", "expires_date_pst": "2015-07-09 01:19:17 America/Los_Angeles", "cancellation_date": "", "cancellation_date_ms": "", "cancellation_date_pst": "" }, ], "latest_receipt": "MIIu0QYJKoZIhvcNAQcCoIIuwjCCLr4CAQExCzAJBgUrDgMCGgUAMIIeggYJKoZIhvcNA" // 最新レシートのBase64文字列 }}
重要そうなものだけ説明します。
status: 0
が返るためです。クライアントでの検証はセキュアではないため、基本的にはサーバを経由した検証を推奨します。
AWAはこの通りではありませんが、一般的なレシート検証の場合、最低限このような検証が必要になります。
現状では6種類あります。
Sandboxでは、時間が早回しで進みます。
Sandbox上の自動更新は6回までで、1ヶ月だと、半年(6回)で自動更新しなくなるので注意してください。
また本番では設定から購読の停止ができますが、Sandboxでは設定が無く停止できないので待つしかないです。
iOSはAndroidと違って毎回レシートを送信する必要があります。
以下の流れで購読期限を更新します。
自動購読を利用する際はレシートによる復元機能が必要になります。ない場合、審査時にリジェクトされます。
ユーザの使用しているAppleIDが同じであれば、どの端末でも同じレシートを再生成することができるため、その再生成したレシートを使って課金履歴と一致するユーザに再ログインさせます。
リストアは以下のメソッドで行います。
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
トランザクション結果は、以下のデリゲートメソッドで受け取ります。
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
レシートを再生成できたら、先ほどのレシート検証フローで「履歴のuserIdと一致する」場合、該当ユーザの情報を返却すれば良いでしょう。
現在AWAではlite, standardの二種類の購読プロダクトがあります。lite->standardへのアップグレード(二重購読)も可能です。また二重購読状態でstandardを解約すれば、standardの期限後に自動的にliteにダウングレードします。
リリースするまでこのような二重購読を実装しているアプリはAppStore上で見当たらなかったため、そもそも審査に通るのか不明でした。
※Evernoteなど、有名な購読アプリでのプラン変更はお問い合わせベースで対応だったため。
WWDCでAppleのエンジニアにもヒアリングしてみましたが、彼らも「大丈夫だと思うが、審査チームがOKかは提出してみないとわからない」と言った回答でした。
結論としてはAWAがリリースできているので二重購読はAppleとしてもOKということになります。
二重購読の場合、アップグレードやダウングレードの処理であったり、複数のレシートをチェックする処理などがありやや複雑になります。しかし「今すぐ上位プランに変更したいのに下位プランをキャンセルして期限まで待たないといけない」「今すぐ変えるためには必ずお問い合わせをしなきゃいけない」といったユーザにとって不利益な実装にはしたくなかったため、今回この対応をしました。
App内課金機能の開発中は、Sandbox環境に接続して課金操作を検証する事になります。
この時に使用するAppleIDは専用に発行するSandboxアカウントになるのですが、Sandboxアカウントは機能が制限されているため本番アカウントであればアクセスが可能な管理/設定画面等にアクセスできません。
そのため、「テストしたいのに出来ない」ケースが度々ありました。
具体的にテストが出来ず困ったのは以下の機能です。
子供が購入しようとした時、親のAppleIDに対して請求される仕組みがあるのですが、この機能の一部がSandbox環境ではテストできませんでした。
本来は子供のアカウントで購入の承認を求める操作を行うと親のアカウントにプッシュ通知が届くのですが、これが何故か届いてくれません。
(親のアカウントはクレジットカード情報を設定する必要があるために本番アカウントを使用しなければならなかったので、それも関係しているかもしれません。)
子供側の端末に親のアカウント情報を入力して直接承認するケースのみ、テスト可能でした。
Sandboxアカウントでは購読管理画面にアクセスできず、解約操作が行えません。
6月頃まではTestFlight環境では本番環境用のAppleIDを利用できる仕様だったのですが、7月頃からSandboxアカウントしか使えなくなりました。
また、同時に購読の自動更新の更新感覚もそれまでの「12時間」から「5分」に変わってしまいました。
仕様変更前はTestFlightで本番アカウントが利用できていたため、テスターは手持ちのiPhoneで普段使いをしながらテスト出来ていたのですが、仕様変更後はTestFlightでの購入操作時にSandboxアカウントのAppleIDにログインしなおす必要が出てきてしまい、日常的に利用しながらのテストが不可能なケースが出てきてしまいました。
TestFlight環境では、購入操作を行うまではレシートが存在しないため「レシートがない=課金操作を行ったことがない」とみなして処理を進めることができました。
しかし、AppStoreからダウンロードしたアプリではインストール直後の時点で既にレシートがローカルに存在するようで、この条件が成り立たなくなってしまいました。
最終的に、上記の条件を前提に設計していたアーキテクチャを見直す必要がありました。
iOSの自動購読課金の実装をする上で「実装時に知りたかった」と思っていた情報をまとめてみました。レシートのフィールドはドキュメントにも書いてないフィールドなどがあったりなど、どれが正しいのか分からずとても苦労しました。
今後iOSアプリで自動購読を導入する方にとって少しでも参考になれば幸いです。
AWAの課金実装および今回の記事を書くにあたってAWAのiOSエンジニアである岐部さん(@beryu)には非常にお世話になりました。本当にありがとうございます。
AWAサーバサイドエンジニアの辻(jun06t)です。
前回の続きで今回はAndroidの月額課金のための実装について書かせていただきます。
基本的な流れは前回と同じになってます。
※1:開発中にプラットフォーム側の仕様変更があったため、記載している内容は情報が古い可能性があります。
※2:記載している動作は十分に調査できていないものも含んでいるため、内容が不正確である可能性があることをご了承ください。
Androidでの流れです。AndroidはGoogleが署名を発行してくれるのでiOSよりもセキュアに検証できます。
レシートと書いてありますが、実際は後述するsubscriptionId
,token
というフィールド名で扱われます。
また署名検証をする場合は決済時の全フィールドをサーバ(上図ではAWAサーバ)へ送信してください。
署名を検証することで、クライアントから送信されるレシートが改竄されていないか確認することができます。
SHA1でハッシュ化された署名なので、golangの場合以下のように検証します。
署名の復号に使う公開鍵はGooglePlayDeveloperConsoleで確認できます。
const ( base64EncodedPublicKey = "--- your app's public key ---")func verify(receipt interface{}, signature string) (bool, error) { // prepare public key decodedPublicKey, err := base64.StdEncoding.DecodeString(base64EncodedPublicKey) if err != nil { return false, fmt.Errorf("failed to decode public key") } publicKeyInterface, err := x509.ParsePKIXPublicKey(decodedPublicKey) if err != nil { return false, fmt.Errorf("failed to parse public key") } publicKey, _ := publicKeyInterface.(*rsa.PublicKey) // decode signature decodedSignature, err := base64.StdEncoding.DecodeString(signature) if err != nil { return false, fmt.Errorf("failed to decode signature") } // generate hash value from receipt b, err := json.Marshal(receipt) if err != nil { return false, fmt.Errorf("failed to JSON marshal") } hasher := sha1.New() hasher.Write(b) hashedReceipt := hasher.Sum(nil) // verify if err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA1, hashedReceipt, decodedSignature); err != nil { return false, nil } return true, nil}
署名検証するレシートのフィールドは以下の通りです。
次はサーバ - GooglePlayAPI間の検証処理について述べていきます。
GooglePlayAPIへは以下のリクエストを投げます。
レスポンスは以下の様な情報が返ります。
{ "kind": "androidpublisher#subscriptionPurchase", "startTimeMillis": 1437567760835, "expiryTimeMillis": 1445631222776, "autoRenewing": true}
また失敗時は以下の様なエラーが返ります。
iOSと違って前述の署名検証とGoogleへのリクエストが成功していれば正しいレシートです。
ただし別ユーザが不正にその正規レシートを送信した可能性を考慮して、レシートのdeveloperPayload
にuserID
を埋め込み、リクエストを送信したユーザのuserID
と一致するかを検証する必要があります。
以下の4種類あります。
購読時に発行されたtokenはキャンセルするまで変わりません。したがって毎回クライアントからレシートを送る必要はありません。サーバだけで検証できます。
purchaseToken
を用い、サーバからGoogleの検証APIを叩くexpiryTimeMillis
を見て購読期限日を更新iOSと異なり最初に送信されたレシートを使えばいつでも購読状態を確認できるので、クライアントでは特に処理は入れずサーバで1日1回程度チェックすれば良いでしょう。
AndroidではGooglePlayがアップグレード、ダウングレードの機能を提供しています。変更後は変更前の購読状態が自動でキャンセルになります。ですのでこの機能を用いればiOSと異なりユーザが二重購読する状態にはなりません。
ただし実装する上で、以下のように請求金額および購読期間の変化があるため注意が必要です。
テストアカウントだと24時間で期間が切れます。
iOSのSandbox環境と違って短くはならないため、翌日になるまで待つ必要がありました。
これはどうしようもなかったためAWAのAndroidエンジニアである沖本さんが自作しました。以下のStack Overflowでgistリンクを貼っています。
How to upgrade/downgrade subscriptions in Android InAppBilling?
先に説明したように、ダウングレード時は先に余分に支払ったお金の分、購読期間が延長されます。
しかしテストアカウントでのダウングレードでは期間は延長されませんでした。
GooglePlayAPIのレスポンスにはautoRenewing
という自動購読状態かどうかのフラグがあります。
AndroidのPlayStoreなどからキャンセルした後はこのフラグがfalse
に変わりますが、15~30分ほど経つまで変わりませんでした。
ユーザとしてはキャンセルしたはずなのに、GooglePlayAPIから返る値は未だキャンセルされていない、とみなされるため、アプリ内でキャンセル済みか表示する場合は定期的にautoRenewing
の状態をチェックする必要があります。
レシートの検証ではorderIdは特に使用しません。しかしながらGooglePlayの課金レポートではorderIdベースで課金情報が登録されるため、ユーザサポートなどで必要になったりします。
AWAのサーバはgolangで実装されており、golangでのレシート検証用ライブラリが古いものしかなかったためこちらも自作しました。AppStoreAPI、GooglePlayAPIの両方に対応しています。
正規レシートを公開できないためテストコードは不足していますが、現在AWAで使用していますので動作自体は問題ないと思います。プルリク歓迎してます。
手探りしながら導入した自動購読の課金処理ですが、色んな問題に直面した分きちんとした実装ができたと思っています。
今後ネイティブアプリで自動購読を導入する方にとって少しでも参考になれば幸いです。
AWAでのAndroid課金実装、そしてこの記事を書くにあたって、Androidエンジニアの沖本さん(cre8ivejp)には非常にお世話になりました。本当にありがとうございます。
どうもこんにちは。@stormcat24です。前回寄稿した記事から1年半ぶりになります。
主な仕事は業務中のTwitterで、その傍らでAmebaFRESH!というサービスを絶賛開発してたりしています(この記事が公開される頃にはリリースしてるかしら?)。何かネイティブやりに意気揚々と異動してきたんですが、いつの間にかサーバサイドエンジニアになってました。まあサーバサイドといっても、自分はかなりWebオペレーション寄りなんですけど。
で、今回はFlexible Blue Green Deploymentの仕組みを作ったよっていうお話です。
こんにちは。現在フロントエンドエンジニアへ転向中の神谷(@fuzzy31u)です。 このブログに登場するのは2回目。前回執筆時はAndroid開発をやっていたんですねー。
…あれから3年。 大学院修士課程修了、結婚、出産、産休/育休と怒涛の日々を経て、現在はAWA株式会社でサーバでもフロントでもjsを書く毎日を送っています。
そんな育休明け半年の私が再びエンジニアブログを執筆することになりましたので、今回はママエンジニアとしてどういったワークライフを送っているのかをテーマに筆を執らせていただきます。
AWAでのワークスタイルはとても柔軟です。
子どもが風邪で保育園をお休みしないといけない時や、予防接種などで小一時間抜けなければならない時には在宅勤務が可能です。
こちらは夕会風景です。
Slackで/room
と打つだけでビデオミーティングが開始できるRoomや/hangout
のGoogle Hangoutで行います。
もともと福岡に開発陣がいることもあり、AWAでは席にいても基本Slackで会話をする文化が定着しています。
場所を問わず働けることはママエンジニアとしては大きなアドバンテージではないでしょうか。
最も産前と変わったことはこれ。
いわゆる定時ダッシュを基本とし、日々19時には一区切りつけることを意識して開発をしています。
"19時以降は作業できない”ので業務中はかなりの集中力を必要とします。基本的には精神とテクの部屋に篭り、メールやチャットも連絡事項以外は見ていません。
これはママにもエンジニアにも限った話ではないですが、定時駆動開発はとても健全で仕事のパフォーマンスも上がり、良いサイクルを実現できています。
前述の通り、コアタイムは開発にコミットしているので、メールチェックや実業務に関係するチャネル以外は業務時間外に回します。 それゆえ業務中に溜まったSlackのタイムラインを追いかけながら帰宅するのが日課です。
復帰にあたって心配なことが一つありました。サーバサイドエンジニアとしてサービスを運用をしていく以上、休日夜間問わず、緊急対応はつきものです。仕事と子育てを両立する上で、子どもが高熱の時に対応もできず、自分の首を締めるようなことだけは避けたいと思っていました。
またもともと目に見える画面の開発をしたいという想いは入社当初からあったこともあり、復帰のタイミングでフロントエンドエンジニアへ転身しました。
もちろんフロントエンド開発に緊急対応がないわけではないですが、バックエンドのそれと比べ致命的なシーンは少ないです。
このようにライフステージに応じてキャリアチェンジを行うことで、環境を変えていくこともママには必要だと考えています。
そんな私がAWAで携わっているのは「レーベル様向けダッシュボード」の開発です。
AWAというと対ユーザ向けアプリのイメージが強いですが、その裏には実際に楽曲を提供してくれているレーベルさんたちがいてサービスが成り立っています。
このツールは再生数や視聴者数、再生数ランキングの統計や速報値をグラフィカルにレーベル様へフィードバックすることで、楽曲提供の側面からもAWAに価値を見出してもらうことを目指しています。
こちらは簡単なシステム構成要素です。本稿では深くは触れませんが、Google BigQueryから大量のログデータを取得し最終的にグラフとして表示するので、ここの処理をいかに最適化するかがアプリケーションのパフォーマンスの肝となります。
AWAチームはユーザ向けプロダクトに携わっているメンバーがほとんどなので、1人異色な開発を行っていると言えます。
しかし初めての技術領域にも関わらず、フロントからインフラ(オーケストレーションツールによるEC2インスタンス、AMIの構築、ミドルウェアの設定変更など)まで触ることができる上に、主席エンジニアの名村に直接レビューをしてもらえるというありがたい環境ですので、惜しみなくスキルアップに精を出しています。
例えばランチは基本的に外で美味しいものを食べるようにしています。
朝から子どもを追いかけ回して保育園へ送った後、20分かけて通勤、午前の開発と、走り続けてやっと訪れる休息なのでランチの時間は重要です。授乳中のため家ではあまり飲めない濃い目のコーヒーを必ず飲み、リフレッシュの時間を意識的に設けています。
また月に1,2回は飲み会にも参加し、大好きなお酒も嗜んでチャージします。
充実したワークライフを送るためにはメリーとハリーが重要です。
至極当たり前のような話ですが。
仕事を定時で切り上げたとしても平日夜に子どもと過ごせる時間は3,4時間。 (一緒に過ごすというよりはお風呂や寝かしつけによる戦争ですが…)
時間はどうしても短くなってしまう分、家では極力スマホをいじらず一緒に過ごす時間の濃さに重きをおくようにしています。
時間は有限ですのでチェックするSNSの数も絞りました。
ポイントとしてはこんな感じでしょうか。
フルタイムで仕事を続けつつ子育ても楽しむためには多少はわがまま言ってもいいと思います。 そしてライフステージにあわせて自らの手で環境を変えていくことも、仕事と子育てを両立する上では重要です。
あんまり気負わず、ちゃんと睡眠時間も確保し、仕事でも子育てでも最高のパフォーマンスを出すことが充実したワークライフを送る上では一番大事。そう私は考えています。
柔軟なワークスタイルを選択できるエンジニアは、子育てしながらでも仕事を続けやすい職業のひとつなのではないでしょうか。
技術本部の須藤(@strsk)です。前回から約1年ぶりの投稿です。
部署名は変わりましたが、相変わらずAmebaのソーシャルゲーム全般のインフラとプラスでピグも担当しています。いろんな理由でコラ画像を作る機会は減ってきました…。
さて、もう既に1ヶ月が経とうとしていますが、AWSが毎年ラスベガスで開催しているカンファレンス、re:Invent2015に参加してきたのでそのレポートになります。社内勉強会でも発表していてスライドはアップしたんですが、残念ながらアメブロにはembedできないのでリンクだけ貼っておきます。技術系のブログを書くには辛い仕様ですね!
re:Invent2015参加レポ // Speaker Deck
今回の記事では印象に残っているセッションについて詳細を記しておきます。(※英語の解釈に間違いがあるかもしれません(・_・;))
サーバーサイドエンジニアの田中です。ずっとソーシャルゲームやってましたが最近動画系に異動になりました。
re:Invent2015に参加してきましたが、自分が参加したセッションの中ではAPI GateWay / LambdaといったAWSサービスを使ったマイクロサービス、サーバーレスに関するセッションが多かったかなと思いますので、そのあたりをピックアップして書いていきたいと思います。
ちょうどAPI Gatewayの東京リージョンでの利用開始やLambdaの新機能発表もこのタイミングであり、Scheduled Eventなど嬉しい機能もあって、おお!と思ったのを覚えています。
こんにちは pnskです。前回から1年ちょっぴり経ったエントリー。
先日、CaFeLaTeでLT大会をやってきたので、レポートです
CaFeLaTeとは
サイバーエージェントグループ全体の有志の女性エンジニアたちが、「ゆるく楽しく」をモットーに、そのとき楽しそうと思った事をやるグループです。
名前の由来は、個人的にカフェラテと元素記号が好きだったので・・もごもご
スペル間違えてるのか?と言われたりするけど、わざとです。。
ロゴは、Amebaのデザイナーさんが作ってくれました。
「頭の双葉の部分は、何か素敵なものが生まれますように♪という意味がこもっています。」とのこと。素敵です
そんなこんなで、色々集まってきたのですが、今回はLT大会をやりたい衝動にかられ、みんなでLT大会をしてきました。
↓今回のLTのタイトル画像を優しいデザイナーさんがまたしても作ってくれました
LT大会のタイトルをbeshari(べしゃり)と勝手につけてたんですけど、関西人にしか通用しないのかなんなのか、「べしゃりってなんですか??」と聞かれてしまいました。ローカルな言葉だったんですかね。。
さてはて。
テーマは自由で発表を募集したところ、本当にとても自由で楽しい発表をきくことができました
※社外秘多めのためざっくり内容
Amebaのエンジニアと開発体制の歴史
Slack活用術
ポケモンとセキュリティーの脆弱制診断やインフラのお話
アドテクのシステムのお話
全社システムの成り立ちやサポート体制について
アドテクのデータ分析のお話
フィジカルコンピューティングのお話
Ameba画像基盤の中身の話
リーン開発のお話
ウエディング業界のIT系あるある
海外ソフトウェアライセンスの交渉術
ウエディングサイトのアクセスビッグウェーブをいかに予測するかのお話
以下、beshariのフォトログ
準備の様子
会場は文房具カフェをお借りしました
何人かの人たちが早めにきて、準備のお手伝いをしてくれ・・・・るとみせかけてお買い物をされていました
受付の様子
彼女は受付です。
キーボードを叩くスピードが受付とは思えないほどでしたが、受付でした。
ターミナルが見えたりすることもありましたが、受付に違いないはずでした。
発表前の様子
LT大会が始まる前に、みんなお酒やお料理を食べて談笑してました
LT大会の様子
みんな準備ができたところで開始~
みんな思い思いの発表をしていました!
5分の発表時間を、平均的に超え・・
みんな真剣に聞いてますね
最後に集合写真
普段仕事で関わる人も、そうでない人もいましたが、
みんな楽しそうに話を聞いたりコミュニケーションを取っていて、LT大会やって良かったです。
何より、運営メンバーの私たちがとても楽しかった
「女性エンジニア」とういう軸は
楽しく気軽に何かやるのにちょうどいいスケールで、とてもいいきっかけだなと思っています。
これからも、何か楽しい事を思いついたら、みんなでやっていきたいですね
おまけ
翌日の反省会。
「思いつきのわりに、楽しかったね~」とか言ったり言わなかったり・・・
日付 | 担当者 | 内容(予定) |
---|---|---|
12/1 | @kakerukaeru | CA Advent Calendar 2015 + 公式エンジニアブログの紹介 |
12/2 | magie_pooh | |
12/3 | principia_ca | |
12/4 | gomachan7 | |
12/5 | blackenedgold | |
12/6 | kaelaela | |
12/7 | stormcat24 | |
12/8 | kuro_m88 | |
12/9 | k_enoki | |
12/10 | principia_ca | |
12/11 | toguri | |
12/12 | matsuokah | |
12/13 | yudppp | |
12/14 | huydx | |
12/15 | chohey | |
12/16 | sitotkfm | |
12/17 | principia_ca | |
12/18 | strsk | |
12/19 | ceeflyer | |
12/20 | nghialv | |
12/21 | k66dango | |
12/22 | taizo | |
12/23 | sambaiz | |
12/24 | principia_ca | |
12/25 | kakerukaeru |
はじめましてー。 サイバーエージェント入社2年目のAndroidエンジニアな@magiepoohです!
業務ではもっぱらAndroidを書いています。(楽しい!)
@kakerukaeruさんに続き、アドベントカレンダー2日目を担当させていただきますーヽ(=´▽`=)ノ
現在、僕はAmebaFreshという動画サービスのAndroid版を開発中で、言語はJavaではなくKotlinを使っています。 Kotlinという言語について書き出すとアメブロの最大記事本文文字数(全角20,000文(半角40,000文字))を超えそうなので、Kotlinに関しては @satorufujiwaraさんや@AAkiraあたりがKotlinアドベントカレンダーに書くことを期待しつつ、全く別のお話を!!
今回はAndroidのライブラリを開発して感じたことや社内制度について、ざっくりと紹介できればなと思います!よろしくお願いしますー!
サイバーエージェントにはGitHubスターインセンティブという制度があります。
GitHubスターインセンティブとは、エンジニアが自身のコードをGitHub(github.com)にオープンソースとして公開し、リポジトリのスター数に応じてインセンティブが得られる制度です。
インセンティブ内容としてはGitHubのスモールプランが会社負担になったりします。(さらに、スター数によってはもっといいことがあったり・・・!?)
個人的にインセンティブがあることによって、会社がGitHub活動を公に推奨してくれていることはとてもありがたい!と感じています。ヽ(=´▽`=)ノ
そもそもライブラリを作ろう(&公開しよう)と思うまでも、かなりのハードルがある気がします。僕の場合は今年の3月に会社の若手数人で行った14ピュアネイティブ強化合宿というものがきっかけです。
すごいスター数が付いているライブラリを公開している@wasabeefさんや@ogaclejapanさんを目指し若手でもなにか作ろうぜ!!というノリのものでした。
が・・・!!!
僕自身その時作ったものはとてもひどいもので本気で闇に葬りたいものができまして、、、完全な黒歴史というやつで惨敗でした、、、ほんとに(´;ω;`)
もちろん、参加者の中には2泊3日で素敵なライブラリを作った人もいましたので、一応チラッと紹介しておきますw
SAHistoryNavigationViewController
Transporter
ここで重要なのは、だいたい最初のアウトプットは闇ばっかりということですw
はじめから完璧なものを作りきろうとすると途中で投げ出したくなるかもしれないし、なかなか完成はしませんし、結局アウトプットができません。僕みたいに一度闇を味わってもいいじゃないですか!!w
とにかく、この合宿のおかげでライブラリを出すというハードルは個人的にはかなり下がりました。
闇を生み出してから月日は流れ、とある実装をしていたときによりスタイリッシュに、より簡単に実装したいなという欲求が生まれました。
具体的には、RecyclerView(リストの行間)に線を引くときにデフォルトではできないし、指定した箇所だけさくっと線を引きたいというものでした。
そもそもライブラリってある問題に対してのアプローチ方法を提案するものだと思うんです。「〇〇が超絶めんどくさいから簡単にできるようにしてみたけど、どうかな?」くらいの。そういう点で自分の中でもっとこうしたいと思えるものを発見するだけで、ライブラリの種は見つけることができそうです。
で、さくっと作って公開してみました。
詳しくはREADMEに書いてあるのですが、各々のViewTypeに対して線を指定してやることができます。 個人的には問題点に対してコンパクトに解決できたライブラリだと感じています。
RecyclerView.ItemDecoration decoration = ItemDecorations.vertical(this) .first(R.drawable.shape_decoration_green_h_16) .type(DemoViewType.LANDSCAPE_TILE.ordinal(), R.drawable.shape_decoration_cornflower_lilac_h_8) .type(DemoViewType.LANDSCAPE_ITEM.ordinal(), R.drawable.shape_decoration_gray_h_12_padding) .type(DemoViewType.LANDSCAPE_DESCRIPTION.ordinal(), R.drawable.shape_decoration_red_h_8) .last(R.drawable.shape_decoration_flush_orange_h_16) .create();
ライブラリの公開方法を学ぶことができます。ここでもbuild.gradleとの格闘が続きかなりハマりました・・・。
現在はかなり簡単になっているようなので、そこまでハマることはないかもしれません。
一番大きいのはこれですね!!
ライブラリを使う人がどうやって使ってくれるかなということを想像して実装することによって、より綺麗なインタフェースを提供しなければいけないというプレッシャーが生まれます。 (普段の業務でももっと意識しろよといろいろな方に怒られそうですが・・w)
僕が作ったライブラリにはIssueが1つしかついてませんが(笑)、そこでも感謝されるととてもありがたいものです。
作ってよかったなと感じることができます。
先ほど、インタフェースを意識するとありますが、他のライブラリの実現方法がより気になりだしたりします。 たとえば、@kgmyshinさんのGoreinu(ライブラリ名とアイコンが素敵w)を見ていると
Goreinu.install(this);
と書くだけで動作します。
こういうライブラリを見るとどうなってるんだろ、自分だと思いつかないなーとかいろいろな感動を得ることができます。
そして、これ、超絶おすすめなんですけど、よくライブラリをウォッチしてスターしている人をフォローする!例えば、弊社のAndroidな人でいうと@wasabeefさんや@ogaclejapanさんをフォローしておくと自動的にキュレーションされるので、面白いライブラリを見つけやすくなりますw
コードの書き方を意識したり、情報に敏感になれるのでとりあえず小さいライブラリでも公開してみるのがまずはいいかなぁと! 最初から完全なものを作る必要もなさそうという感じでやってみると楽しいことに出会えます!
ってことで!つらつらと書いてきましたが、明日は@principia_ca
さん! (・・・・公式ブログってこと・・?ここのブログに誰かが書くってことかな??) あんまりよくわかってませんが!よろしくお願いします!!w
それでは、みなさん、よいエンジニアライフを~ @magiepoohでした~~ ヽ(=´▽`=)ノ
こんにちは、森野耕平(@kohei_april20)と申します。
最近は社内向けのResponsive Image as ServiceとしてHayabusa(https://hayabusa.io/)というものを開発・運用しています。今回はそのResponsive Image as Serviceとその取り組みについて紹介させて頂きたいと思います。
近年、スマートフォンなど接続デバイスが急増してきたことに加え、リッチなユーザー体験のニーズの高まりによって、ウェブサービスの開発・運用の複雑さが増してきいます。多くの場合ウェブページにおけるデータの大半を占めているのは画像で、この画像を効率よく最適な形で配信することが重要になってきます。
そのためにはデバイスに応じて解像度別の画像や、フォーマット別の画像(例えばwebpが利用可能なデバイスにはwebpを使うなど)を生成したり、更に減色や圧縮といった様々な処理を行ったりした画像群を用意する必要があります。その上で、更に接続デバイスを検知し最適な画像を選択して配信する仕組みを整えなければなりません。
実際私が過去に担当していたプロジェクトでも、解像度別の画像を生成したり複数の圧縮プログラムを通した画像群をリリース前に事前に生成するような仕組みを用意して行っていました。扱っていた画像が多かったこともあって、運用中に追加分の画像を処理するだけでも結構な時間がかかり、素早いリリースが難しく苦労をしていました。
この問題を解決する手法としてResponsive Image as Serviceというものがあります。Responsive Image as Serviceとは、画像に動的な操作をするための情報をURLのパラメータなどから取得し、オンデマンドでレスポンシブ画像を生成・配信をサービスとして提供するものです。オリジンに存在する画像をマスタとして使い、要求に応じて画像処理を行いレスポンシブ画像を生成する、画像プロキシのように動作します。
このようにサービスとして画像関連の煩雑な手続き、変換にかかる負荷、キャッシュの取り扱いなどを担うことで、これらの問題を気にすること無く開発・運用ができる体勢を築くことができ、効率化を図ることが出来ます。
URLから次のような情報を取得します。
画像のファイルパスからオリジナル画像を特定し、パラメータからその画像に行う処理に関する情報(リサイズ、クロップ、圧縮、フォーマット変換など)を取得します。
例)
Query String Parametersの場合http://<server>/image.jpg?width=300&height=200&format=webpドットセパレータによるパラメータの場合
http://<server>/image.w300.h200.jpg
他にもクッキーやヘッダーからの情報を用いる場合もあります。ヘッダーは次のようなものを利用します。
また、Http Client Hintsという、クライアントにとって最適なコンテンツを配信するために必要な情報を入れるヘッダがドラフトとして上がっているので、将来的にこのヘッダからwidth、height、DPRなどの情報を取得できるようになっていくと思います。
Hayabusaはオープンソース化を視野に入れて社内で開発しているResponsive Image as Serviceの実装で、現在一部の自社サービス向けに機能を提供しており、現在半年ほど運用実績があります。
https://hayabusa.io/test/abema.png
https://hayabusa.io/test/abema.w100.h50.fitcrop.png
https://hayabusa.io/test/abema.pngquant(color2).png
https://hayabusa.io/test/abema.jpg
Node.jsで実装をしていて、次のようなメインのモジュールから構成されています。
hayabusaモジュールはResponsive Image as Serviceとしての機能を汎用的に実装したもので、hayabusa-serverモジュールがこれを利用して社内ユースに特化する形で実装したウェブアプリケーションになっています。
画像処理機能にあたるプラグインは次のようなものがあります。
これらの機能は基本的に裏で様々は画像処理プログラムを利用するラッパーのような役割をしていて、リサイズ・クロップ・フォーマット変換は主にImageMagick、GraphicsMagick、最適化ではpngquant、optipng、zopfli、gifsicle、jpegrecompressなどを利用しています。
処理のフローは下図のように、クライアントが画像のファイルパスと処理内容を含んだリクエストを送り、hayabusaが受け取ったURLやヘッダ情報から処理対象のオリジナル画像と処理内容を判別、オリジナル画像を処理するための画像処理プログラムをアレンジして順に実行し、最終結果画像をクライアントへ返すという流れになっています。
下図はアーキテクチャを簡易的に表したものです。https://<hayabusa-server>/foo/img1.webpにユーザがアクセスすると、fooという文字列にマッピングされたオリジンサーバ<origin-foo>に置かれているimg1.*となる画像をオリジナル画像として処理した結果を得ます。
オンデマンドで画像を処理するため、どうしてもHayabusaサーバ側でオリジンから画像をダウンロードする時間とそれを処理する時間がかかってしまいます。そのため、配信を極力速く、そして画像処理の負荷をむやみに増やさないために、CDNとキャッシュサーバの二段構えでできるだけキャッシュが効くように構成しています。
今後の展望として次のような機能拡張を予定しています。
この記事はCyberAgent エンジニア Advent Calendar 2015の8日目の記事です
こんにちは!サイバーエジェント アドテクスタジオ新卒の黒崎 (@kuro_m88) と申します。vagrant@vagrant-ubuntu-wily-64:~$ wget -qO- https://get.docker.com/ | sh
vagrant@vagrant-ubuntu-wily-64:~$ sudo aptitude install openvswitch-common openvswitch-switch
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker run -d --name router1 --net=none --privileged -v /lib/modules:/lib/modules kurochan/vyos:1.1.6 /sbin/initvagrant@vagrant-ubuntu-wily-64:~$ sudo docker run -d --name router2 --net=none --privileged -v /lib/modules:/lib/modules kurochan/vyos:1.1.6 /sbin/initvagrant@vagrant-ubuntu-wily-64:~$ sudo docker run -d --name router3 --net=none --privileged -v /lib/modules:/lib/modules kurochan/vyos:1.1.6 /sbin/initvagrant@vagrant-ubuntu-wily-64:~$ sudo docker run -d --name router4 --net=none --privileged -v /lib/modules:/lib/modules kurochan/vyos:1.1.6 /sbin/init
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES99a121b7984a kurochan/vyos:1.1.6 "/sbin/init" 2 seconds ago Up 2 seconds router43599cf751e0c kurochan/vyos:1.1.6 "/sbin/init" 8 seconds ago Up 8 seconds router3035e17a8fd43 kurochan/vyos:1.1.6 "/sbin/init" 15 seconds ago Up 14 seconds router266bb97a046c5 kurochan/vyos:1.1.6 "/sbin/init" 21 seconds ago Up 20 seconds router1
vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-vsctl add-br switch1vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-vsctl add-br switch2vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-vsctl add-br switch3vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-vsctl add-br switch4
vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch1 eth0 router1 --ipaddress=10.0.1.1/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch2 eth1 router1 --ipaddress=10.0.2.1/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch1 eth0 router2 --ipaddress=10.0.1.2/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch3 eth1 router2 --ipaddress=10.0.3.1/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch2 eth0 router3 --ipaddress=10.0.2.2/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch4 eth1 router3 --ipaddress=10.0.4.1/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch3 eth0 router4 --ipaddress=10.0.3.2/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch4 eth1 router4 --ipaddress=10.0.4.2/24
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker exec -it router1 ping -c 2 10.0.1.2PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.227 ms64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.058 ms
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker exec -it router1 ping -c 2 10.0.3.2connect: Network is unreachable
router1 => router2(10.0.1.2): orouter1 => router3(10.0.2.2): orouter1 => router4(10.0.3.2): xrouter2 => router3(10.0.4.1): xrouter2 => router4(10.0.3.2): orouter3 => router4(10.0.4.2): o
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker exec -it router1 /bin/vbashvbash-4.1# su - vyosvyos@vyos:~$ configure[edit]vyos@vyos# set interfaces loopback lo address 1.1.1.1/32[edit]vyos@vyos# set protocols ospf area 0 network 10.0.0.0/16[edit]vyos@vyos# set protocols ospf parameters router-id 1.1.1.1[edit]vyos@vyos# commit[edit]vyos@vyos# saveSaving configuration to '/config/config.boot'...Done[edit]vyos@vyos# exitexitvyos@vyos:~$ exitlogoutvbash-4.1# exitexit
set interfaces loopback lo address 1.1.1.1/32set protocols ospf area 0 network 10.0.0.0/16set protocols ospf parameters router-id 1.1.1.1
set interfaces loopback lo address 2.2.2.2/32set protocols ospf area 0 network 10.0.0.0/16set protocols ospf parameters router-id 2.2.2.2
set interfaces loopback lo address 3.3.3.3/32set protocols ospf area 0 network 10.0.0.0/16set protocols ospf parameters router-id 3.3.3.3
set interfaces loopback lo address 4.4.4.4/32set protocols ospf area 0 network 10.0.0.0/16set protocols ospf parameters router-id 4.4.4.4
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker exec -it router1 /bin/vbashvbash-4.1# su - vyosvyos@vyos:~$ show ip routeWARNING: terminal is not fully functionalCodes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,I - ISIS, B - BGP, > - selected route, * - FIB routeC>* 1.1.1.1/32 is directly connected, loO 10.0.1.0/24 [110/10] is directly connected, eth0, 00:05:53C>* 10.0.1.0/24 is directly connected, eth0O 10.0.2.0/24 [110/10] is directly connected, eth1, 00:05:53C>* 10.0.2.0/24 is directly connected, eth1O>* 10.0.3.0/24 [110/20] via 10.0.1.2, eth0, 00:02:23O>* 10.0.4.0/24 [110/20] via 10.0.2.2, eth1, 00:01:33C>* 127.0.0.0/8 is directly connected, lo
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker exec -it router1 ping -c 2 10.0.3.2PING 10.0.3.2 (10.0.3.2) 56(84) bytes of data.64 bytes from 10.0.3.2: icmp_req=1 ttl=63 time=0.363 ms64 bytes from 10.0.3.2: icmp_req=2 ttl=63 time=0.091 ms
router1 => router2(10.0.1.2): orouter1 => router3(10.0.2.2): orouter1 => router4(10.0.3.2): orouter2 => router3(10.0.4.1): orouter2 => router4(10.0.3.2): orouter3 => router4(10.0.4.2): o
vagrant@vagrant-ubuntu-wily-64:~$ sudo docker run -d --name router5 --net=none --privileged -v /lib/modules:/lib/modules kurochan/vyos:1.1.6 /sbin/initvagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-vsctl add-br switch5vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch5 eth2 router4 --ipaddress=10.0.5.1/24vagrant@vagrant-ubuntu-wily-64:~$ sudo ovs-docker add-port switch5 eth0 router5 --ipaddress=10.0.5.2/24
set interfaces loopback lo address 5.5.5.5/32set protocols ospf area 0 network 10.0.0.0/16set protocols ospf parameters router-id 5.5.5.5set protocols ospf default-information originate always
vyos@vyos:~$ show ip routeWARNING: terminal is not fully functionalCodes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - ISIS, B - BGP, > - selected route, * - FIB routeO>* 0.0.0.0/0 [110/1] via 10.0.1.2, eth0, 00:01:46 * via 10.0.2.2, eth1, 00:01:46C>* 1.1.1.1/32 is directly connected, loO 10.0.1.0/24 [110/10] is directly connected, eth0, 01:07:55C>* 10.0.1.0/24 is directly connected, eth0O 10.0.2.0/24 [110/10] is directly connected, eth1, 01:07:55C>* 10.0.2.0/24 is directly connected, eth1O>* 10.0.3.0/24 [110/20] via 10.0.1.2, eth0, 01:04:25O>* 10.0.4.0/24 [110/20] via 10.0.2.2, eth1, 01:03:35O>* 10.0.5.0/24 [110/30] via 10.0.1.2, eth0, 00:42:41 * via 10.0.2.2, eth1, 00:42:41C>* 127.0.0.0/8 is directly connected, lo
vyos@vyos:~$ traceroute 10.0.123.123traceroute to 10.0.123.123 (10.0.123.123), 30 hops max, 60 byte packets 1 10.0.2.2 (10.0.2.2) 0.072 ms 0.025 ms 0.014 ms 2 10.0.4.2 (10.0.4.2) 0.055 ms 0.023 ms 0.023 ms 3 10.0.5.2 (10.0.5.2) 0.047 ms !N 0.033 ms !N *
サイバーエージェントと言えばこの blog や pigg のようにゲームやコミュニティのサービスを思い浮かべる方が多いと思いますが、広告系のアドテクサービスも幅広く展開しています。
アドテクって何?という方はこちらの adtech blog や日本一やさしいアドテク教室を見て頂けるとイメージしやすいかと思います。
アドテク本部ではデータセンターにプライベートクラウドを持っていて、たくさんのアドテクのサービスがプライベートクラウドで動いています。
プライベートクラウドについていくつか外部のイベント等でお話をさせて頂いたりしているのでご存知の方もいらっしゃると思いますが、アドテク本部のプライベートクラウドは OpenStack が採用されています。
OpenStack についてはこの場では詳しく説明しませんが、日本仮想化技術株式会社の宮原さんが書かれた Think IT の記事「OpenStack のアーキテクチャを理解しよう」がとてもよくまとまっていますので是非御覧ください。
最近は OpenStack を採用する会社も増えてきているようです。
(サイバーエージェントも実は結構前から OpenStack を使っているんですよ)
OpenStack は開発のスピードが非常に早く、半年に 1 回大規模な version up が行われます。
(最近の動向ですとコンポーネント毎に個別で version up をしていく方向に変わりつつあります)
OpenStack の version はちょっと特徴的でアルファベット順に OpenStack Summit が開催される国の地名が付きます。
2015年12月現在の最新 version は Liberty ですが、その次は Mitaka と続きます。
M ではついに日本の地名が付くことになりました。
OpenStack Summit と言えば、今年は5月にカナダで開催されましたが10月に日本の東京で開催されました。ご存知の方がいるかもしれませんが、東京の Summit では Keynote に登壇させていただきました!
さて OpenStack のお話はここまでにして本題に入りましょう。
本日は OpenStack のコンポーネントである Magnum というものを動かしてみようと思います。
Magnum ですよ Magnum。なんか名前がかっこいいですよね!
最近はコンテナ技術が盛んになってきていて使えることが当たり前になりつつあります。
その一方で煩雑になりがちなコンテナ達をどうやって管理(オーケストレーション)しようか、という話題も出てきました。
そこでコンテナのオーケストレーションツールとして Kubernetes や docker swarm 等が出てきたのはみなさんご存知の通りです。
OpenStack Magnum はこれらのオーケストレーションツールを OpenStack で管理できるようにするためのコンポーネントになります。
つまり、コンテナのオーケストレーションツールをオーケストレーションするものということになりますね。
OpenStack にはオーケストレーション機能を提供する Heat というコンポーネントがあります。
OpenStack Magnum はこの Heat と連動し、OpenStack 内にオーケストレーションツールをオーケストレーションします。
出展:http://www.openstack.org/assets/pdf-downloads/Containers-and-OpenStack.pdf
この Magnum ですが、まだ正式な OpenStack のコンポーネントには入っていません。Liberty には間に合わないと思いますが、Mitaka もしくはその次あたりで入ってくるかもしれませんので今のうちから慣れておきましょう!
OpenStack Magnum は当然ながら OpenStack のコンポーネントですので既に稼働している OpenStack 環境が無いと動きません。
OpenStack を動かすためにはいくつか方法がありますが、比較的簡単に構築できるのは devstack や packstack といったツールになります。
devstack や packstack を使った OpenStack の install の仕方はここでは省略しますが、いろいろ記事が公開されてますのでそれを見ながら構築してください。
devstack を使って構築していればこちらの手順書の通り、devstack/local.conf
の [[local|localrc]]
のセクションに下記の設定を入れます。
ここでは最新の stable である kilo の branch を指定しています。
enable_plugin magnum https://github.com/openstack/magnum stable/kilo
magnum-api
及び magnum-conductor
のプロセスが上がっているはずです。packstack で OpenStack を作成した場合や RDO の repository を使って OpenStackを構築した場合は rpm で OpenStack Magnum を入れることができれば理想的なのですが、RDO には現在のところOpenStack Magnum の rpm は提供されていません。
ですが rpm 自体は実は copr に存在していてこちらにありますので、こちらを入れるのが楽だと思います。
パッケージやツールを使わないでインストールする場合は git から clone してインストールすれば OK です。
方法は公式の Document の通りにすればいけると思います。
$ git clone https://git.openstack.org/openstack/magnum$ cd magnum$ sudo pip install -e .
インストール以外にも幾つか作業が必要ですのでやっておきましょう。
# create the magnum conf directory$ sudo mkdir -p /etc/magnum# copy sample config and modify it as necessary$ sudo cp etc/magnum/magnum.conf.sample /etc/magnum/magnum.conf# copy policy.json$ sudo cp etc/magnum/policy.json /etc/magnum/policy.json
この作業は devstack を使っていれば devstack がやってくれるので必要ありません。
自前でインストールした場合には既存の OpenStack 環境に Magnum の情報を登録する必要があります。
まず OpenStack の backend の Database に magnum が使用する database を作成します。ここでは magnum という Database と magnum という user を作成しています。
$ mysql -h <your_openstack_db_ip> -u<user> -p<pass> mysql <<EOFCREATE DATABASE IF NOT EXISTS magnum DEFAULT CHARACTER SET utf8;GRANT ALL PRIVILEGES ON magnum.* TO ‘magnum'@'%' IDENTIFIED BY 'password'EOF
次に OpenStack の backend の rabbitmq に magnum ユーザーを追加します。ここでは vhost を / にしています。環境によって変えて下さい。
$ rabbitmqctl add_user magnum password$ rabbitmqctl set_permissions -p / magnum “.*" ".*" ".*"
そして認証(Keystone) に service を追加して endpoint を作成します。
endpoint の ip アドレスは magnum-api や magnum-conductor が動いているサーバーの ip にしてください。
$ keystone service-create --name=magnum \ --type=container \ --description=“OpenStack Container Service"$ keystone endpoint-create --service=magnum \ --publicurl=http://<api_server_ip>:9511/v1 \ --internalurl=http://<api_server_ip>:9511/v1 \ --adminurl=http://<api_server_ip>:9511/v1 \ --region <your region>
設定ファイルは /etc/magnum/magnum.conf
になります。いろいろ設定する項目がありますが、最低限これらを設定しておけば動いてくれると思います。rpm_backend には rabbitmq を使用することを前提にしてます。rabbitmq 以外にも ZeroMQ とか使えます。debug や verbose については必要に応じて有効にしてください。database の connection や rabbitmq の接続情報は先ほど作成した user や password を指定してください。
[DEFAULT]debug = trueverbose = truerpc_backend = rabbit[api]host = 0.0.0.0[database]connection = mysql://magnum:password@<user_openstack_db_ip>/magnum[keystone_authtoken]auth_uri = http://<your_keystone_ip>:5000identity_uri = http://<your_keystone_ip>:35357admin_user = magnumadmin_password = passwordadmin_tenant_name = service[oslo_messaging_rabbit]rabbit_hosts = <your_rabbitmq_ip>:5672rabbit_userid = magnumrabbit_password = passwordrabbit_virtual_host = /
conf ファイルを設定する際には直接編集することももちろん可能ですが、OpenStack の conf ファイルは何かと肥大化しており(設定項目がやたら多かったり、ほとんどコメントだったりしますが)
いちいち開いて編集するのもめんどくさくなっています。そこで conf を編集するツール openstack-config が用意されています。
RedHat 系を前提に作られていますが、openstack-config であれば ubuntu 系でも使うことができます。その場合は crudini を別途 install してください。
openstack-config を使用する場合はこんな感じになります。
$ sudo openstack-config --set /etc/magnum/magnum.conf DEFAULT debug true$ sudo openstack-config --set /etc/magnum/magnum.conf DEFAULT verbose true$ sudo openstack-config --set /etc/magnum/magnum.conf DEFAULT rpc_backend rabbit$ sudo openstack-config --set /etc/magnum/magnum.conf api host 0.0.0.0$ sudo openstack-config --set /etc/magnum/magnum.conf database connection mysql://magnum:password@<user_openstack_db_ip>/magnum$ sudo openstack-config --set /etc/magnum/magnum.conf keystone_authtoken auth_uri http://<your_keystone_ip>:5000$ sudo openstack-config --set /etc/magnum/magnum.conf keystone_authtoken identity_uri http://<your_keystone_ip>:35357$ sudo openstack-config --set /etc/magnum/magnum.conf keystone_authtoken admin_user magnum$ sudo openstack-config --set /etc/magnum/magnum.conf keystone_authtoken admin_password password$ sudo openstack-config --set /etc/magnum/magnum.conf keystone_authtoken admin_tenant_name service$ sudo openstack-config --set /etc/magnum/magnum.conf oslo_messaging_rabbit rabbit_hosts <your_rabbitmq_ip>:5672$ sudo openstack-config --set /etc/magnum/magnum.conf oslo_messaging_rabbit rabbit_userid magnum$ sudo openstack-config --set /etc/magnum/magnum.conf oslo_messaging_rabbit rabbit_password password$ sudo openstack-config --set /etc/magnum/magnum.conf oslo_messaging_rabbit rabbit_virtual_host /
一番簡単なのはそのまま叩いてしまうことです。
forground で起動されます。
$ /path/to/magnum-api$ /path/to/magnum-conductor
systemd が配下で動かす場合は下記の service ファイルを使って下さい。
先ほどの rpm を使えばこちらは配置されているはずです。
openstack-magnum-api.service
[Unit]Description=OpenStack Magnum API ServiceAfter=syslog.target network.target[Service]Type=simpleUser=magnumExecStart=/usr/bin/magnum-api[Install]WantedBy=multi-user.target
openstack-magnum-conductor.service
[Unit]Description=Openstack Magnum Conductor ServiceAfter=syslog.target network.target qpidd.service mysqld.service openstack-keystone.service tgtd.service openstack-glance-api.service openstack-glance-registry.service openstack-nova-api.service openstack-nova-objectstore.service openstack-nova.compute.service openstack-nova-network.service openstack-nova-volume.service openstack-nova-scheduler.service openstack-nova-cert.service openstack-cinder-volume.service[Service]Type=simpleUser=magnumExecStart=/usr/bin/magnum-conductor[Install]WantedBy=multi-user.target
現時点では OpenStack Magnum は OpenStack の管理画面(Horizon) から操作することはできません。
ですが Horizon の pluggin として ui の開発が始まっているようです。
将来的には管理画面からポチポチできるようになると思いますが、現時点ではおとなしく python-magnumclient を使いましょう。
virtualenv で入れておくことをおすすめします。
$ git clone https://git.openstack.org/openstack/python-magnumclientcd python-magnumclientpip install -e .
これで OpenStack Magnum を使う準備が整いました!
まずは baymodel を作成します。baymodel っていうのは次に作る bay の定義と考えて下さい。
baymodel では使用する image や keypair、オーケストレーション(COE)等を定義します。
この際に使用する image や keypair が無いと作ることができませんので、まずは準備をします。
現時点では OpenStack Magnum が support しているのは Fedora Atomic
と CoreOS
だけのようです。(CentOS Atomic も多分動くと思いますが)
ここでは Fedora の Atomic を登録してみます。
まずは https://getfedora.org/ja/cloud/download/atomic.html から qcow2 のイメージを取得します。
記事を書いている時点での最新は Fedora-Cloud-Atomic-22-20150521.x86_64.qcow2 のようです。
$ wget https://download.fedoraproject.org/pub/fedora/linux/releases/22/Cloud/x86_64/Images/Fedora-Cloud-Atomic-22-20150521.x86_64.qcow2
その後 OpenStack の image 管理(Glance) に登録します。
$ glance image-create --file ./Fedora-Cloud-Atomic-22-20150521.x86_64.qcow2 --is-public True --disk-format qcow2 --container-format bare --property os_distro=fedora-atomic --name Fedora-Cloud-Atomic-22-20150521 --progress
注意点としてはイメージの meta 情報の os_distro に fedora-atomic を入れておくことです。
これが無いと bay の作成に失敗します。
続いて keypair を登録します。
keypair の名前はなんでもいいです。ここでは適当に default という名前で登録します。
$ nova keypair-add --pub-key openstack_key.pub default
それから使用する external network の uuid を調べておきます。
$ neutron net-show public --format value --fields id931ef60c-d132-4bfa-9136-182c7b723d79
これで準備が整いました。
さっそく baymodel を作ってみます。
今回は dockerswarm を使う baymodel swarmbaymodel を作ってみます。
(最新の magnum だと network の指定は uuid ではなくて名前でいけるようになったようです)
$ magnum baymodel-create --name swarmbaymodel --image-id Fedora-Cloud-Atomic-22-20150521 --keypair-id default --external-network-id 931ef60c-d132-4bfa-9136-182c7b723d79 --flavor-id m1.small --coe swarm
作成した baymodel は magnum baymodel-list
で見ることができます。
$ magnum baymodel-list+--------------------------------------+---------------+| uuid | name |+--------------------------------------+---------------+| f433f2d9-3592-452f-9642-d7ffadc52b2f | swarmbaymodel |+--------------------------------------+---------------+
また、magnum baymode-show swarmbaymodel
とすることで詳細を表示することができます。
baymodel が作成できたので bay を作成してみましょう。
$ magnum bay-create --name swarm bay --baymodel swarmbaymodel --node-count 1
magnum-bay list
を見ると CREATE_IN_PROGRESS
になっていると思います
$ magnum bay-list
+--------------------------------------+----------+------------+--------------+--------------------+| uuid | name | node_count | master_count | status |+--------------------------------------+----------+------------+--------------+--------------------+| 0643a67f-fce8-41aa-b8c8-ee2854174b15 | swarmbay | 1 | | CREATE_IN_PROGRESS |+--------------------------------------+----------+------------+--------------+--------------------+
この時内部的には Heat が動いています。
$ heat stack-list+--------------------------------------+-----------------------+--------------------+----------------------+| id | stack_name | stack_status | creation_time |+--------------------------------------+-----------------------+--------------------+----------------------+| 9d850fc8-7940-4523-aa1a-a5aee8931a28 | swarmbay-ur35j3pus33u | CREATE_IN_PROGRESS | 2015-09-11T00:57:35 |+--------------------------------------+-----------------------+--------------------+----------------------+
heat resource-list swarmbay-ur35j3pus33u
とするとどんな処理をしているのか確認することができます。
Heat は管理画面(Horizon) から確認することができますので、bay-create をすると管理画面から確認することができます。
しばらくすると CREATE_COMPLETE になると思います。
それではさっそくコンテナを動かしてみます。docker/whalesay
を使って鯨に Magnum と言わせてみましょう。
$ magnum container-create --bay swarmbay --name whale_magnum --image docker/whalesay --command "cowsay magnum”$ magnum container-list+--------------------------------------+--------------+---------+| uuid | name | status |+--------------------------------------+--------------+---------+| 481ac897-d922-45ac-a4e6-59198f780f6f | whale_magnum | Stopped |+--------------------------------------+--------------+---------+
コンテナを実行してみます。
$ magnum container-start whale_magnum$ magnum container-logs whale_magnum ________< magnum > -------- \ \ \ ## . ## ## ## == ## ## ## ## === /""""""""""""""""___/ === ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ \______ o __/ \ \ __/ \____\______/
無事に動きました!
もちろんコンテナを実行させている swarmbay の中にも login することが可能です。magnum bay-create
した際に Heat によってインスタンスが作られていると思います。
今回は fedora の Atomic で作成してますので fedora@ で login します。
中に入ると docker が動いているのが確認できます。
$ ssh fedora@192.168.0.209[fedora@sw-inltgwe3sxe-0-qgs7s3w5s5cg-swarm-node-raxqpfeo45ud ~]$ sudo docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES00eb818dfe19 docker/whalesay:latest "cowsay magnum" 5 minutes ago Exited (0) 4 minutes ago whale_magnumbe9d3809265b swarm:0.2.0 "/swarm join --addr 2 weeks ago Up 2 weeks 2375/tcp swarm-agent
もっといろいろなコンテナイメージを使って動かしてみようと思ったのですが、ちょっと時間が無いので今日はこれまで!
要望があれば時間作って検証してみたいと思っています。
今回は baymodel に docker swarm を使いましたが、他にも Kubernetes と mesos にも対応しています。
Kubernetes については最近は が GA となり、version も 1.0 がリリースされました。
その際に内部の API が v1beta3 から v1 に変わりましたが、まだ Magnum では v1 には対応してないので Kubernetes の GA 版が動かせないのが残念なところですね。
OpenStack Magnum いかがでしたでしょうか。
まだ絶賛開発中のコンポーネントではあるのでサービスで使う場合は十分に注意する必要がありますが、OpenStack が動いている開発環境とかでコンテナを動かしてみたい場合とかには向いてると思います。
現在のところはコマンドラインでしかいろいろ操作できませんが、OpenStack の管理画面 (Horizon) の pluggin も開発が始まっているようです。
Magnum の他にも開発中のコンポーネントはたくさんあり、例えば DNS 管理の Designate、メッセージングサービスの Zaqar、鍵管理の Barbican、ファイル共有の Manila などたくさん進行中です。
OpenStack はすべて OSS で出来ています。
新しいサービスの開発や既存の bugfix 等、誰にでも可能です。
興味がありましたら是非 OpenStack へコントリビュートしてみましょう!
実は自分もちょっとだけ貢献しました。これからもちょこちょこ貢献していきたいと思います!
こんにちは。maginemuです。
ここのところはiOS/Android向けにPIGG PARTYというサービスの開発を行っています。
iOS9アプリケーションのIPv6への対応期限が刻々と迫ってきました。
今回IPv6への対応に際してトラブルがあり、原因調査をしたのですが、
もともと全然IPv6の知識が無く、ネットワークに関する知識も大して無く、
かろうじて知っているコマンド群もIPv6でうまく動かなかったりしてハマったので、
知ってる人には当たり前だと思いますが、紹介してみることにします。
その前に少しだけ前提を。
AppleではiOS9以降、IPv6でアプリケーションが正しく動作する必要があるとしています。
そしてその対応期限は2016年初頭とされていて、それ以降は対応していないと審査が通らなくなる…!
対応の仕方についてはこちらを参照します。
※実際にアプリケーションをIPv6に対応させることについては、この記事では触れません。
早い話が、El Capitanではoptionを押しながらインターネット共有を選択すると、NAT64ネットワークを作成するチェックボックスが出ます。
適当なマシンでこのオプションでインターネット共有を行い、そこにアクセスすればIPv6の環境が試せます。
ひとつ気をつける必要があるのが、インターネットに接続したい場合にはこのマシンがWi-Fi以外でインターネットに接続できる
必要があります(有線LANとか)。
なので最近のMacbook Proなどを使う場合、Ethernetアダプタとかが必要になるかもしれないです。
これで手元ではiMacxxxx...
という感じのネットワークが見つかるようになりました。
これに接続して試していきます。
何はともあれ、諸々確認しましょう。手元の環境は MacBook Pro (Retina, 15-inch, Mid 2014), OS X 10.11.1 です。
ひとまずifconfig
。
...en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500ether aa:bb:cc:dd:ee:ffinet6 fe80::a8bb:ccff:fedd:eeff%en0 prefixlen 64 scopeid 0x4inet6 2001:xxxx::a8bb:ccff:fedd:eeff prefixlen 64 optimistic autoconfinet6 2001:xxxx::xxxx:xxxx:xxxx:xxxx prefixlen 64 autoconf temporarynd6 options=1<PERFORMNUD>media: autoselectstatus: active...
en0
のインタフェースのところだけ抜粋。inet6
となっているところがIPv6アドレスですね。
IPv6では、ホストひとつに対して複数のアドレスを持つことができます。
fe80
から始まるアドレスはリンクローカルなアドレスで、ルータを越えない同一ネットワーク内のアドレスです。
下位64ビットはイーサネットアドレス48ビットを24ビットずつに区切り、間にfffe
を挟み、
上から7ビット目を反転させたものになっています。
2001
から始まるものはグローバルアドレスです。正確には2000::/3
からE000::/3
がグローバルユニキャストアドレスだそうです。2001
から始まるものが、いまのところIANAによって割り振られているようです。
グローバルアドレスのうち上のほうはステートレス設定というやつで生成されたもののようです。
これはルータから得られたプレフィクスとMACアドレスベースで生成されるものです。
temporary
というのは一時アドレスというやつで、上記ステートレス設定のものだと、
アドレスからマシンが特定できてしまうので、定期的に変わるアドレスも用意されているみたいです。
IPアドレスの体系に関してはとりあえず、3つのタイプがあって、
またそれぞれ到達範囲:スコープでも分類できるという感じです。
IPv6のアドレス(ステートレス)自動設定については下記などに書いてあります(なるべく確実そうなやつ)。
とりあえず自分にpingしてみようかな!
$ ping 2001:xxxx::a8bb:ccff:fedd:eeffping: cannot resolve 2001:xxxx::a8bb:ccff:fedd:eeff: Unknown host
なん…だと…
IPv6だと何か違うのか…! (ping ipv6とかでググる)
知ってれば何てことないんですが、コマンド違うんですよね。。ping6
というのがあります。
$ ping6 2001:xxxx::a8bb:ccff:fedd:eeffPING6(56=40+8+8 bytes) 2001:xxxx::a8bb:ccff:fedd:eeff --> 2001:xxxx::a8bb:ccff:fedd:eeff16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=0 hlim=64 time=0.084 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=1 hlim=64 time=0.210 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=2 hlim=64 time=0.213 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=3 hlim=64 time=0.235 ms^C--- 2001:xxxx::a8bb:ccff:fedd:eeff ping6 statistics ---4 packets transmitted, 4 packets received, 0.0% packet lossround-trip min/avg/max/std-dev = 0.084/0.185/0.235/0.059 ms
よしよし。
知ってますよ。nslookup
ですよね。
$ nslookup google.comServer: 2001:xxxx::1Address: 2001:xxxx::1#53Non-authoritative answer:google.comAddress: 216.58.220.206
あれ…v4のアドレスだけですね。。
あー、さては。
$ man nslookup6No manual entry for nslookup6
違うの。。。
(ググる)
なるほど。-type=AAAA
というオプションか。
$ nslookup -type=AAAA google.comServer: 2001:xxxx::1Address: 2001:xxxx::1#53Non-authoritative answer:has AAAA address 64:ff9b::d83a:dcceAuthoritative answers can be found from:google.com
おお。取れた。Wikipediaによると
In the Domain Name System hostnames are mapped to IPv6 addresses by AAAA resource records, so-called quad-A records.
だそうです。
wget
してみましょう。
$ wget google.com--2015-12-09 18:46:38-- http://google.com/Resolving google.com... 64:ff9b::d83a:dcce, 216.58.220.206Connecting to google.com|64:ff9b::d83a:dcce|:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.co.jp/?gfe_rd=cr&ei=fvhnVumcLMT98we005DwBg [following]--2015-12-09 18:46:38-- http://www.google.co.jp/?gfe_rd=cr&ei=fvhnVumcLMT98we005DwBgResolving www.google.co.jp... 64:ff9b::adc2:75f7, 64:ff9b::adc2:75f8, 64:ff9b::adc2:75ff, ...Connecting to www.google.co.jp|64:ff9b::adc2:75f7|:80... connected.HTTP request sent, awaiting response... 200 OKLength: unspecified [text/html]Saving to: 'index.html'[ <=> ] 19,287 --.-K/s in 0.009s2015-12-09 18:46:38 (2.10 MB/s) - 'index.html' saved [19287]
おお。普通にいけた。helpを見ると
-6, --inet6-only connect only to IPv6 addresses.
というオプションがあるみたいですね。
IPアドレスだと…
$ wget 64:ff9b::d83a:dcce--2015-12-09 18:49:38-- ftp://64/ff9b::d83a:dcce=> 'ff9b::d83a:dcce'Resolving 64... 0.0.0.64Connecting to 64|0.0.0.64|:21... failed: Network is unreachable.
おお、ftpだと思われている。では。
$ wget http://64:ff9b::d83a:dccehttp://64:ff9b::d83a:dcce: Bad port number.
ぐぬぬ。ですよねーーー
IPv6のアドレスを指定するときは、そのままだとポート番号と区別が付きません。[]
で括る必要がありそうです。
$ wget http://\[64:ff9b::d83a:dcce\]--2015-12-09 19:32:48-- http://[64:ff9b::d83a:dcce]/Connecting to [64:ff9b::d83a:dcce]:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.com/ [following]--2015-12-09 19:32:48-- http://www.google.com/Resolving www.google.com... 64:ff9b::adc2:75f3, 64:ff9b::adc2:75f2, 64:ff9b::adc2:75f1, ...Connecting to www.google.com|64:ff9b::adc2:75f3|:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.co.jp/?gfe_rd=cr&ei=UANoVpuWO6Kg8weR_oCoDQ [following]--2015-12-09 19:32:48-- http://www.google.co.jp/?gfe_rd=cr&ei=UANoVpuWO6Kg8weR_oCoDQResolving www.google.co.jp... 64:ff9b::adc2:75f8, 64:ff9b::adc2:75f7, 64:ff9b::adc2:75ef, ...Connecting to www.google.co.jp|64:ff9b::adc2:75f8|:80... connected.HTTP request sent, awaiting response... 200 OKLength: unspecified [text/html]Saving to: 'index.html'[ <=> ] 19,215 --.-K/s in 0.03s2015-12-09 19:32:49 (553 KB/s) - 'index.html' saved [19215]
無事取れました。
一旦ローカルホストでサーバを立てて、それに接続して検証したかったりしますよね。
上述しましたが、IPv6が有効になっているホストでは
まずリンクローカルアドレスが割り当てられます。
このアドレスはiMacやNAT64の環境を用意しなくても使えそうです。
とりあえず適当なhttpサーバを。。node
とかnpm
とかexpress
が入ってる前提で
($ npm install -g express
)
$ express app && cd app && npm install && DEBUG=app:* npm startcreate : appcreate : app/package.jsoncreate : app/app.jscreate : app/publiccreate : app/public/javascriptscreate : app/public/imagescreate : app/public/stylesheets...node ./bin/wwwapp:server Listening on port 3000 +0ms
ここで、本質的じゃないのですが、nodeでlistenするときに、IPを指定しないとIPv6でうまくlistenされないようなので
ちょっとソースコードを変更しておきます。
diff --git a/bin/www b/bin/wwwindex a8c2d36..e79c926 100755--- a/bin/www+++ b/bin/www@@ -25,7 +25,7 @@ var server = http.createServer(app);Listen on provided port, on all network interfaces.*/-server.listen(port);+server.listen(port, '::0');server.on('error', onError);server.on('listening', onListening);
で
$ wget localhost:3000--2015-12-11 15:24:56-- http://localhost:3000/Resolving localhost... ::1, 127.0.0.1, fe80::1Connecting to localhost|::1|:3000... connected.HTTP request sent, awaiting response... 200 OKLength: 170 [text/html]Saving to: 'index.html'2015-12-11 15:24:56 (40.5 MB/s) - 'index.html' saved [170/170]
よしよし。ではIPアドレスで。
$ wget http://\[fe80::a8bb:ccff:fedd:eeff\]--2015-12-11 15:32:03-- http://[fe80::a8bb:ccff:fedd:eeff]:3000/Connecting to [fe80::a8bb:ccff:fedd:eeff]:3000... failed: No route to host.
あれ。。。
$ ping6 fe80::82e6:50ff:fe14:18beping6: UDP connect: No route to host
あれー。。。
調べてみると、どうやら-I
オプションが必要そうです。man ping6
を見てみると
-I interfaceSource packets with the given interface address. This flag applies if the ping destination is a mul-ticast address, or link-local/site-local unicast address.
なるほど。今は送出先がリンクローカルユニキャストアドレスです。
リンクローカルなアドレスを指定するときには、なにかとインタフェース名とか、scope_id
といった値を
指定する必要がありそうです。
ifconfig
を再度見直してみると
inet6 fe80::a8bb:ccff:fedd:eeff%en0 prefixlen 64 scopeid 0x4
%en0
とか、scopeid 0x4
とか書いてありますね。
ということで、
$ ping6 -I en0 fe80::a8bb:ccff:fedd:eeffPING6(56=40+8+8 bytes) fe80::a8bb:ccff:fedd:eeff%en0 --> fe80::a8bb:ccff:fedd:eeff16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=0 hlim=64 time=0.100 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=1 hlim=64 time=0.104 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=2 hlim=64 time=0.209 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=3 hlim=64 time=0.232 ms^C--- fe80::a8bb:ccff:fedd:eeff ping6 statistics ---4 packets transmitted, 4 packets received, 0.0% packet lossround-trip min/avg/max/std-dev = 0.100/0.161/0.232/0.060 ms
うまく通りました。
しかし、wgetは未だにうまくできず。。%en0
を付けてみて、
$ wget http://\[fe80::a8bb:ccff:fedd:eeff%en0\]:3000http://[fe80::a8bb:ccff:fedd:eeff%en0]:3000: Invalid IPv6 numeric address.
だめか。。ちなみにcurl
だと…?
$ curl http://\[fe80::82e6:50ff:fe14:18be%en0\]:3000 ⏎<!DOCTYPE html><html><head><title>Express</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Express</h1><p>Welcome to Express</p></body></html>%
いけたーーーー
scope_idの指定の仕方に関しては、けっこう実装依存のようです。
上記を見てみると、a numeric zone index can be used in the second 16-bit word
とあります。では試しに
$ wget http://\[fe80:4::a8bb:ccff:fedd:eeff\]:3000--2015-12-11 17:36:13-- http://[fe80:4::a8bb:ccff:fedd:eeff]:3000/Connecting to fe80:4::a8bb:ccff:fedd:eeff|fe80::a8bb:ccff:fedd:eeff|:3000... connected.HTTP request sent, awaiting response... 200 OKLength: 170 [text/html]Saving to: 'index.html'100%[======================================================================================>] 170 --.-K/s in 0s2015-12-11 17:36:13 (20.3 MB/s) - 'index.html' saved [170/170]
おお。いけました。なんだそれ。。。
ちなみに、GoogleChromeのアドレスバーに打ち込んだところ、
http://[fe80:4::a8bb:ccff:fedd:eeff]:3000/
この形式では取得できましたが、
http://[fe80::a8bb:ccff:fedd:eeff%en0]:3000/
の場合検索ワードと見なされてしまいました。
http://[fe80::a8bb:ccff:fedd:eeff%25en]:3000/
とかエンコーディングしてみても変わらずでした。
ローカルホストのIPでアクセスするだけでこれほどハマるとは。。。
少しですが、IPv6環境について紹介させて頂きました。
最初はコマンド自体が合ってなかったので、どこに問題があるのかの
切り分け自体できなくてハマりました。。
IPv4からアドレス空間が広くなっただけでなく、
知らない仕様が色々あることを痛感しました。。
調べてみると、200X年とかの記事が結構ヒットしますね。
今後IPv6環境もいよいよ増える流れかと思いますので、
トラブルシューティングのための準備を整えて行きたいところです。
ではでは!
この記事はCyberAgent エンジニア Advent Calendar 2015 の24日目の記事です。
メリークリスマス!エンジニアブログ運営チームの柿島です。今年は、前半は1月に共著でHBaseの本を出したり、5月にサンフランシスコで行われたHBaseCon 2015で発表したりとHBaseのことばかり考えている日々を過ごしていました。後半は、アメーバでのAmazon Web Services(以下、AWS)やGoogle Cloud Platform(以下、GCP)といったパブリッククラウドの利用が増えてきたため、7月から急遽部署を異動してパブリッククラウドのコスト削減や利用戦略などを考えるチームを作って日々色々な社内プロジェクトと連携をしています。
今回は、その流れでパブリッククラウド、特にGCPのコストについて書きたいと思います。今回の記事で利用する画像はマスクが多く入りますがご了承ください。(イブ公開の記事なのにクリスマスまったく関係ないのもご了承ください)
アメーバでは、AWS,GCPともに支払いアカウント(billing account)は1つでそのアカウントに各プロジェクトのアカウントを紐づける運用をしています。この仕組みはでは支払いアカウントにまとめて請求が来るため、支払い面では便利なのですが、各プロジェクトには請求書が届きません。データセンターに大量のサーバを購入して利用するオンプレミス環境と違って、パブリッククラウドの場合、利用するプロジェクトのエンジニアがコストを意識できる環境を作ることがコスト削減の近道ではないかと考えているのですが、コストがわかりづらいとそもそも意識することが難しいよねというお話になります。
AWSではCost Explorerといった仕組みがあるので、各アカウントでもコストの推移などが調べやすい環境があるのですが、GCPでは各プロジェクトで当月のコストは見えても(Fig.1)、過去の履歴は支払いアカウントでしか見れません(Fig.2)。ただ、この支払いアカウントは支払いに関する情報や他のプロジェクトの情報が見えてしまうといったこともあって最低限のメンバーにしか権限を与えておりません。そのため、各プロジェクトのメンバーから問い合わせがあったときに私からそのプロジェクトのみに絞った過去のデータを渡すという対応をしていました。
Fig.2 支払いアカウントでは過去のデータも見れるが、権限は最低限のメンバーのみに絞っている
ただ、この問い合わせ対応ベースで過去の履歴を渡すのは、利用プロジェクト側も私たちも面倒なためコストを気軽に確認するという環境ではありませんでした。これを改善したいというのが今回のモチベーションです。
1. Export billing data を有効にする
まずは、支払いアカウントで Export billing dataを有効にします。この機能はdailyのGCPの使用量や料金を指定したGCSのbucketに2日遅れくらいでcsvやjsonで出力してくれる機能です。有効にする方法や出力されたファイルがどんな項目を持つかは https://support.google.com/cloud/answer/6293835?hl=en をみてください。この機能を有効にします(Fig.3)。ここで指定するGCSのbucketを持つプロジェクト、GCSのbucketの権限は支払いアカウントに触れるメンバーに限定してあります。
Fig.3 Export billing dataを有効にする
この例ではcsvのフォーマットでExport billing dataを有効にしています。有効にした日以降のdailyのファイルが2日遅れぐらいで出力されますので、数日後に指定したGCSのbucketを見てみるとこのようにdailyでcsvが出力されていることがわかります(Fig.4)。
Fig.4 指定したGCSのbucketには指定したフォーマットでファイルが出力される
Fig.7 別のプロジェクトの先月のコストが高かった要素のランキング TOP5(GCEのインスタンスが高い)
あけましておめでとうございます!!!
エンジニアブログ運営委員です。
あれ、年が明けたと思ったらもう2週間も経ってるの?
寒中お見舞いのご挨拶のほうがよくない?
年々時間が過ぎるのが早くなっててうっかりしてた?
これはきっと去年の振り返りを怠っているせい。
すっきりと新しい年を始めるためにも、2016年最初の記事は去年のアクセス数ランキングです。
読み逃した記事、気になる記事はお時間のある時に読んでいただけたらと思います。
2015年公開記事アクセス数ランキング!
第10位