AWS CodeDeployで遊んでみた

会社でCodeDeployを使ってたけど、使ったことなかったので遊んでみた。

AWS CodeDeployとは?

https://aws.amazon.com/jp/codedeploy/ より

AWS CodeDeploy は、Amazon EC2 インスタンス、およびオンプレミスで稼働するインスタンスを含む、さまざまなインスタンスへのコードのデプロイを自動化するサービスです。

シンプルなデプロイであればこれで十二分に思える。デプロイを自動化してくれるサービスなのでCI的なことをやりたいのであれば、CodePipelineとかつかうんだろう。

全体図

GitHubにpushするとCircleCIが走るので、テストが通ったらアプリをS3にput後デプロイする。 リージョンは安いからus-east-1

f:id:hs_hachi:20160911005810p:plain

内容

一部でも読んだドキュメント

IAMユーザとロールの作成

  1. APIを叩くためのユーザ(ユーザ名:codeDeployUser)を作成する(このユーザのaccess id をCircleCIに管理画面で登録する) Step 1: Provision an IAM User - AWS CodeDeploy をみてロールを適切に付与する。やりたいことにもよるが,最低限S3とCodeDeploy関連があればOK
  2. CodeDeployで実行するときに必要なデプロイグループ用ロール(ロール名:CodeDeployServiceRole)を作成する
    1. アクセス許可の管理ポリシーに AWSCodeDeployRole を追加する
    2. 信頼されたエンティティを追加する
  3. EC2を作るときに設定するIAMロール(ロール名:CodeDeployDemo-EC2)を作成する
    1. 管理用ポリシーに AmazonEC2FullAccess を追加した(ここは多分ちゃんと絞るべき)
    2. インラインポリシーにポリシー(ポリシー名:ec2-role-permission)を追加
    3. 信頼関係を追加する

CodeDeployServiceRoleの信頼関係

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "codedeploy.us-east-1.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

CodeDeployDemo-EC2のインラインポリシー

 {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::aws-codedeploy-us-east-1/*"
            ]
        }
    ]
}

CodeDeployDemo-EC2の信頼関係

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

S3バケットの追加

特定者からのみのアクセスをうけるように、バケットポリシーにユーザ codeDeployUserとロールCodeDeployDemo-EC2を追加した。

EC2ユーザ作成

2台以上で作ること。

  1. インスタンスを作成するときにIAMロールCodeDeployDemo-EC2を忘れない
  2. CodeDeployのグループの対象インスタンスとなるようにタグを作成するときにキー:Role, 値:WebAppという名前をつけた
  3. AWS CodeDeploy agentをインストールする

CodeDeployの設定

途中で失敗してもアプリの内容はロールバックしないので注意(正常にデプロイしたものがあればそれはそのまま)

  1. アプリケーションを新規で作成する
    • アプリケーション名:sample-app (ここは circle.ymlで使う)
    • デプロイグループ名:SampleAppsGroup (ここは circle.ymlで使う)
    • EC2インスタンスタグ:Role:WebApp
    • サービスロール ARN:CodeDeployServiceRole を選択した
    • デプロイ設定:CodeDeployDefault.OneAtATime ここはお好み

CircleCIの設定

  1. codeDeployUserのaccess Key IDとSecret Keyを登録する

アプリの設定

  1. circle.yml にデプロイの設定を記載
  2. appspec.yml にcodeDeployで実行する内容を記載する。(この辺はあまり調べられてない)

circle.ymlの内容の抜粋

deployment:
  staging:
    branch: master
    codedeploy:
      sample-app: # ここはアプリケーション名と合わせる
        application_root: /
        region: us-east-1
        deployment_group: SampleAppsGroup # ここはデプロイメントグループと合わせる
        revision_location:
          revision_type: S3
          s3_location:
            bucket: バケット名
            key_pattern: my-sample-app-{SHORT_COMMIT}-{BUILD_NUM} # キーパターンは適当

appspec.ymlの内容

version: 0.0
os: linux
files:
  - source: /
    destination: /usr/local/demo-app # アプリ配置先
hooks:
  BeforeInstall:
    - location: deploy/before_install.sh # インストールする前の実行するシェル(ELBから外すとか..)

その他

  • /var/log/aws/codedeploy-agent/codedeploy-agent.log にログが出るので途中で失敗した時は確認する

Jenkins2.0 + http2をつかってみた

Jenkinsには足を向けて寝れないほど毎日使ってる人間なので、めでたくJenkins2が出たのでやってみた
ついでにhttp2使ったらどのぐらいはやくなるのかな?というみたかった。
あとLet's Encryptもつかってみよう

環境

  • jenkins 2.6
  • nginx 1.11
  • AWS(AMI ami-f5f41398) t2.micro US East (N. Virginia)リージョン

結論

http2にすると大体0.5秒〜1.0秒ぐらい速くなった。体感的には若干速くなったと感じる程度。
積極的に使っていっていいと思います


jenkins2 using http1(ssl)


jenkins2 using http2

ざっくりした環境構築

Let' Encryptのインストール

gitを入れる必要がある

git clone https://github.com/certbot/certbot
cd certbot/
./certbot-auto --debug
./certbot-auto certonly --standalone -d <domain>

nginxのインストール

/etc/yum.repos.d/nginx.repo に以下を追加してから yum install nginx でインストール

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/6/$basearch/
gpgcheck=0
enabled=1
priority=1

nginx.confの一部

upstream backend {
    server 127.0.0.1:8080;
}

server {
    listen       443 ssl http2 ;
    listen       [::]:443 ssl http2;
    #listen       443 ssl ;
    #listen       [::]:443 ssl ;
    server_name  <domain;
    root         /usr/share/nginx/html;

    gzip on;

    ssl_certificate "/etc/letsencrypt/live/<domain>/fullchain.pem";
    ssl_certificate_key "/etc/letsencrypt/live/<domain>/privkey.pem";
    # It is *strongly* recommended to generate unique DH parameters
    # Generate them with: openssl dhparam -out /etc/pki/nginx/dhparams.pem 2048
    #ssl_dhparam "/etc/pki/nginx/dhparams.pem";
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers AESGCM:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP;
    ssl_prefer_server_ciphers on;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
        proxy_pass http://backend ;
        proxy_redirect default;

        proxy_set_header   Host             $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_max_temp_file_size 0;

        #this is the maximum upload size
        client_max_body_size       10m;
        client_body_buffer_size    128k;

        proxy_connect_timeout      90;
        proxy_send_timeout         90;
        proxy_read_timeout         90;

        proxy_buffer_size          4k;
        proxy_buffers              4 32k;
        proxy_busy_buffers_size    64k;
        proxy_temp_file_write_size 64k;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

Jenkinsのインストール

sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
yum install jenkins

久しぶりに技術的なことを書けて楽しかった

不具合を分析しすぎてチームに悪影響を与えたかもしれない

担当サービスの品質を担保するというのが仕事の1つなので、不具合の分析などを行うことはよくある。
ここでいう分析とは以下のことを指してる

  1. 不具合がいつからいつまで発生していたか
  2. なぜ発生したか
  3. どのような対応をおこなったのか
  4. ユーザにどのような案内をしたのか
  5. (ソーシャルゲームなので)どのようなお詫び(アイテム補填)をしたのか
  6. どうやったら再発しないようにするか

この辺の分析はある程度の経験やスキルが必要だと思っている。 ユーザへの案内などは

  • 自分が草案を出して、企画職やCSがそれに補完する
    • 例:過去の事例に照らし合わせるとか、ルール上問題ないか
  • 企画職やCSが草案を出して自分が補完する
    • 例: 長文で書くよりも箇条書きにしようとか、時系列で書くとか

という感じでやっているので、まぁ得意不得意を補完してていいかなーとは思える。

今回のメインは「なぜ不具合が発生したか」というところ。
基本全員日々の作業に追われているし、嫌な過去は見たくないものなのであまり調べたがらない。
みんな不都合な過去をなかったことにしたいからソシャゲやってる会社はダメだと叩かれる
が、ある程度エンジニア(特にSIer)をやっていると「なぜを3回繰り返せ」というありがたい教えを聞かされるはずだ。自分は新卒2年目で叩きこまれた。
Web業界にきてだいぶ年数が経つが、若い時に叩きこまれたことは習慣(悪いものは呪いという)のようになるので癖で調べてしまう。

分析する上で色々話をきかせてもらった結果、 チームが必要以上に不具合に敏感になりすぎてる気がする。
敏感になりすぎている人と鈍感なままな人がいるというのが正確な表現

もちろん不具合はないほうがいい。不具合を起こして誰も幸せにならないからだ。
みんな可能なかぎりゼロにすることを心がけてる。 作ってる人みんなで触ってみてる場も儲けてるし、QAテストも時間かけてる。
ユーザへの影響がほとんどないものはさっさと直せばいいし、そこをいちいち分析しても大した答えは出てこないと思う。
変に敏感になり過ぎててなんか硬直してる(動きが遅い)組織になってきてる気がする。

この辺はみんなどういうマネジメントしてるんだろうか

テストの改善案の案

エンジニアのKPTで「CircleCIで走らせてるテストが項目数の割には30分超えてきたから速くしたい」という話をしたけどちゃんと説明できなかったので書く

環境

  • Java8, SpringFramework
  • MySQL
  • Junit, SpockFramework

問題点

テスト実行の最初(いわゆる @Beforeとかsetup)で、マスタデータをJVMメモリに乗せているが、本番環境と同じデータを使ってる。
いちいち作らなくてもいいというメリットがあるが、本番環境と同じデータなのでいかんせんデータ量が多い。
本番環境のデータをそのまま使うとコード上通らない箇所(フェイルセーフしてる箇所)や、昔動いてたけど今は使わなくなったコードがあるのでカバレッジが上がらない。
あと、テスト用のユーザデータ(マスタデータ)を入れるときに、SQLスクリプトを生で実行したり、ヘルパークラス経由で入れてるので、バラバラになってる 2年ほど前に途中でこのチームに入った時に、テストがなかったので「勢いで」自分が入れたのが敗因だった。もう少し戦略的にやればよかったと反省しているが、入れたことへの後悔はしてない

改善案

本番環境とテスト用のデータを分けて、必要な物だけ入れる また、テストデータがバラバラになっているのはコメントが書けるYAMLに合わせる 例

user:
  - # ああいうユーザ
    id: 1
    name: 10
    birthday: 2016-01-01
  - # こういうユーザ
    id: 2
    name: test
    birthday: 2017-01-01

f:id:hs_hachi:20160310032332p:plain

このファイルを用意しておき、テストを書く人がsetup時に読み込ませる。読み込ませるときには一度databaseをdrop→createした上で読み込ませる。(テストが並列化できないが、そもそもJUnitでは並列化できないからいい) あとは既存のデータをYAMLへ変換するツールなどを作ればちょっとは楽になるかな?と思ってる。
ちなみにはてブ上で盛り上がったテスト書かなくてもいいよ的な話は、育ってきた環境やフェーズが違うからどちらが正しいとも言えないと思う。

今更サーバサイドとフロントエンドの責務を考えてみる

まえがき

仕事(Webブラウザ上で動くソーシャルゲーム開発)でSPA(Single Page Application)のサーバ側(いわゆるJSONを返すAPIを作成)を担当していた同僚の自分より若いエンジニアと話していたので自分の考えをまとめておきます。あと、ここではAjaxを使った非同期通信の話も混ぜて話してます。というか、話してる途中でそちらの方へ流れた。
なお、ここに書いてあることは、ずっと前から色々なところで話をされていることなので、書いていて今更感満載の内容になったけど、まぁいいかなと。

話の流れ

「やっぱサーバ側はJSON返却するだけだと楽だよね」という話になったので、「まーそうなんだけどねー何でもかんでもフロント側に任せすぎるのもよくないよね」という話になりました 。
フロントはフロントで色々大変なんだから、サーバとフロントの両方の知識を持ってる人が全体をデザインしないと大変になるよねというのが結論です。

前のプロジェクトで感じたこと

以前のプロジェクトでもAngularをつかったSPAのゲームを作っているチームに途中参加していたけど、サーバ側に比べて、フロント側が大変そうだなぁというのを感じていました。
というのも、1枚のHTMLページを表示した後にサーバ側と複数APIを呼び出して、通信が成功/失敗した時のルーティング処理をして、ブラウザ上にキャラをゴリゴリ動かして、また通信して、返ってきたJSONデータを元にまたキャラをグリグリ動かす。みたいなことをしてました。
ちゃんとした(と言っていいかわからないけど)SPAのサイトは初めてだったけど、開発する人の負荷やブラウザへの負担をかけすぎだよなーというのを感じたのをはっきりとおぼえてます。(フロントエンジニアたちはどうおもってるかは知らない)

自分だったらどうするか?

キャラをゴリゴリ動かしたりするView側の複雑な動きはフロント側の仕事だと思うので、そこはサーバ側はJSONを返すだけにしたいです。

でも、最初に表示するデータを返す箇所はサーバ側でHTML組み立ててもいいと思ってます。というか、無理に全体をSPAにすることにこだわりを持ってないです。
うちのチームだとサーバ側でHTMLを組み立てるようにしても、ERBやfreemarkerなどのテンプレートエンジン上にHTMLを書いていくのはフロントエンジニアの仕事だからなのですがw

また、細かいルーティングなどは開発する人、ブラウザへの負担とかを考慮するとサーバ側でやった方がいいかなと思ってます。

よくあるのが軽く見せるために周りのコンテンツを表示してから、Ajaxを使ってメインのコンテンツを表示することがよくあります。あれに若干のギャップを感じていて、体感はあがるのでそこはわかるんだけど、結局メインコンテンツを見せないと意味ないんだよなぁという思いもあります

また、サブコンテンツ(例えば、ボタンをタップしたらお知らせ情報を表示するとか、キャンペーンバナーとか)はAjaxで持ってきたほうが、ユーザの体感はあがるので積極的に使ったほうがいいなと思います。(ただしリクエスト数には注意)

最後にデータを更新して、その結果を使って何かする(ソシャゲでよくあるボタンタップしてクエストを進める)ものは都度全部の画面描画する必要はないので、Ajax通信で完結させたほうがいいかなと思います

蛇足

前のプロジェクトで管理画面がデータの取得にもAjax通信を使ってサーバとやり取りしていてだいぶ使いづらかったです。
他のメンバーとURLを共有することはよくあるので、ユーザが一意のURLをわかるようにしてあげるといいなと思いました(小並感

ロードス島戦記に学ぶ技術的負債の返し方

1年前にいたプロジェクトに戻ってきて3週間ほど経過しました。
ミーティングが多くてガッツリコードを書くことがなくなりました。
同じフロアにいたため時々飲みに誘ってもらってたので久しぶり感はなかったのですが、コードを見るとだいぶ変わってました。
1クール目はJava8を入れ逃げしたので、ちゃんと使ってくれてるのは嬉しかったです。

サービスを2年ぐらいやっているのでよくも悪くも色々なものが溜まってました。
いわゆる技術的負債です。
技術的負債は今から見ると負債になっているのですが、その頃は時間的、技術的制約でそれがベターだと判断されたものに加えて、運用中に状況が変わって作った時の想定と異なる使われた方をしたとか、大量のデータが蓄積することで発生する問題とか色々あります。 例えば

  • 当初は10連ガチャ想定だったのでinsert文を10回発行で運用できていたが、100連ガチャになったので最大100回のinsertが走るようになった カードアイテムはまとめて付与というのは仕様的に難しい bulk insertを使いたかったが、テーブル分割をしていたためORMマッパーが対応してなかった
  • 運用で溜まったマスタデータが8万件になって、それをすべてアプリ起動時にメモリに入れてる。そのためアプリ起動に時間がかかる ** ローカル環境やCircleCIでのテスト実行時にも本番と同じマスタを使っているのでそれぞれにも時間がかかってる

スタート時はまずユーザにサービスを提供してナンボというところはあるのでしょうがないですね。 全部を綺麗に作りすぎてたら出すタイミングを逃してサービスが終わったなんて本末転倒。 ただ、もうやらないといけない時期かなという判断をしたので改善していきたいなと。何より自分の精神衛生上良くない 最大の懸念は改善をどうやってしていくかですね。パターンはいくつかあると思います。

  1. チーム内外から改善する専用の人を設ける
  2. 週1日の改善dayを設けてみんなで改善する
  3. 気づいた人が随時改善する

どうしようかなーと思ってた矢先に10年以上ぶりにロードス戦記を読んでて傭兵王カシューが治めるフレイム国の軍隊の運用がうまいなーと感心。 そこは正規の騎士団と傭兵団をうまく使い分けてて、きっちり固めに正攻法に真正面から行く時は騎士団で、変則的な相手や柔軟にいきたいときは自由に動ける傭兵団で運用してました。 毎週のようにリリースがあり施策ごとにスケジュールがバラバラでメンバーによって得手不得手があるので、みんなで改善するよりも、ミーティングが多くてフルで開発に入れない自分が最初に流れを作りつつ気づいた人がやっていくというのが一番無難な流れかなと最近思ってます。

次のソシャゲ開発でやったほうがいいなと思うこと

チュートリアル駆動開発

  • チュートリアルがユーザが一番最初に見るところだから、そこで落ちると意味が無い
  • チームとしてはゲームの幹をはっきりするため
  • ここにないものは実装するかどうか検討する

イテレーション0を大切にする

  • Jenkins、ステージング環境、デプロイとか
  • 最低でもステージング環境までは作る

チーム内レビューのクオリティをあげる

  • エラーになるのは論外
  • ボタンなどの素材もできる限り本番に出すものと同じものを。(カード素材は難しいかも)

イテレーションで終わらなかったものを次イテレーションと平行させない

  • というか、あまり作業を平行させない
  • 脳みそのスイッチコスト高い
  • クオリティ落ちる
  • 疲弊する
  • 最初から頑張るではなくて何か対策を打つ。無理に頑張ってもクオリティは落ちる。頑張るは思考停止
  • ただし、最後の最後は踏ん張る

ターゲットとしているユーザのペルソナを作る

  • 年齢とかどういうゲームが好きかとか職業とか、プレイする時間帯とか
  • 暗黙の了解にしてる部分があるので明確化する
  • 萌えゲーだと野郎ばっかりだから考えてて楽しいかは別問題

◯◯ゲームのような感じという言葉を可能な限り使わない

  • ◯◯ゲームと△△ゲームという感じみたいにして組み合わせてもつぎはぎ感がある
  • 似た要素があってはいけないというわけではない。トータルのバランスが大事
  • そもそもレッドオーシャン(どころかブラッドオーシャン)だから、似たもの出してもあんまりヒットしないよね

テーマを決める

  • テイルズ系みたいにちゃんとゲームのキャッチフレーズを決めたい
  • それによってゲームのトーンとか変わると思う

他にもなにかあったと思うけど忘れた 思い出したので3つを追記した