GitHubフローを助けてくれるボットを作った

今回はGitHubフローを助けてくれる簡単なボットを作ったので紹介します。

ボット環境

弊社ではHubotを使ってSlack用のボットを作っています。

github.com

HubotはGitHub製のボットライブラリで、coffeescriptで簡単にかけて、そのままHerokuに簡単に上げることができます。

最初はSlackに移行したときにせっかくならボットを作ろうと思って始めて、ネタ機能が多かったり、新人研修の題材に使っていましたが、現在はユーザー数をログから計算して教えてくれたり、サービスにエラーが発生したら教えてくれたりなど実用的な機能を追加しています。

弊社でのGitHubフロー

開発のフローは基本的に以下のようになっています

  • 実装方針などをissueに書く
  • 実装する
  • プルリクエストを出す
  • レビューする
  • マージする

普通のフローですが、弊社ではブランチ名にもルールを設けています。

(feature|refactor|fix)-[issue No.]-description

例をあげるとfeature-100-implement_user_registrationという感じです。

基本的にプルリクエストはひとつのissueに紐付いているため、プルリクエストがマージされるとそのブランチとissueは不要となるため消すことになっていたのですが、人間はサボる生き物なので徹底されずに、ゴミのブランチやissueが多く残ってしまっていました。

ボットを作った

そこで、プルリクエストをマージしたら関連するブランチとissueを自動的に削除するボットを作りました!

apiUrl = "https://api.github.com/repos/xxxx"
querystring = require('querystring')

module.exports = (robot) ->
  robot.router.post("/github/pr/cleaner", (req, response) ->
    data = req.body
    action = data.action
    pullRequest = data.pull_request
    if action == 'closed' && pullRequest.merged
      repo = pullRequest.head.repo
      name = repo.name
      branchName = pullRequest.head.ref

      # delete branch
      deleteBranchRequest = robot.http("#{apiUrl}/#{name}/git/refs/heads/#{branchName}")
        .header('Authorization', "token #{process.env.GITHUB_TOKEN}")
        .del()
      deleteBranchRequest((err, res, body) ->
        if err
          robot.messageRoom(channel_name, "Deleting #{branchName} was failed")
      )

      # close issue
      issueNumber = branchName.match(/-(\d+)-/)
      if issueNumber[1] != undefined
        number = issueNumber[1]
        console.log("request #{apiUrl}/#{name}/issues/#{number}")
        closingIssueRequest = robot.http("#{apiUrl}/#{name}/issues/#{number}")
          .header('Authorization', "token #{process.env.GITHUB_TOKEN}")
          .patch(JSON.stringify(state: 'closed'))
        closingIssueRequest((err, res, body) ->
          if err
            robot.messageRoom(channel_name, "Closing \##{number} was failed")
        )

      url = require('url')
      query = querystring.parse(url.parse(req.url).query)
      channel_name = query.channel
      user = pullRequest.user.login
      robot.messageRoom(channel_name, "Good job, #{user}!\nLeave the rest to me!")
    response.end('')
  )

これをHerokuに上げた状態でGitHubのWebhookにhttps://xxx/github/pr/cleaner?channel=yyyというのを設定してプルリクエストのフックをとれるようにすればもう動きます。

これにより100%ブランチとissueが整理されるようになりました。

まとめ

このような感じで弊社ではボットを使って開発や運用の効率を上げる取り組みを行なっています。ボットがフローの一部となって人間の負担を減らしてくれるととても助かりますし、ボットにもっとすごいことをやらせてサービスを加速させることもできます。

Circle CI + AWS + Dockerでデプロイを自動化する

今回はタイトルの通り、Circle CI + AWS + Dockerの組み合わせで簡単にサービスのデプロイを自動化した話をします。

弊社サービスWorkinGoodではGItHub flowを採用しており、masterへマージした後に自動的にElastic Beanstalkへ新しいバージョンを作るところまで行っています。依存サービスとの関係もありデプロイするタイミングはこちらが手動でAWSGUI上から行っています。

また、弊社では開発環境構築にDockerを使っていますが、本番環境へのデプロイにも使用しています。

http://blog.matchingood.com/entry/2016/11/11/145246blog.matchingood.com

これを実現するために具体的には以下のものを使っています。

  • GitHub
  • Circle CI
  • Docker
  • AWS Elastic Container Registry (Docker registry)
  • AWS Elastic Beanstalk

この中でもデプロイを圧倒的に簡単にしているのがDockerとElastic Beanstalkの組み合わせです。Elastic BeanstalkとはAWSが提供しているサービスの一つで、あらかじめ用意しておいた環境定義ファイルによって自動的にAuto Scalingとデータベースを構築してくれるサービスです。

こちらに簡単な説明があったので見てみるといいと思います。

dev.classmethod.jp

かなりお手軽にこの仕組みを作ることができるので簡単に手順を示していきたいと思います。

circle.yml

最初にcircle.ymlにどのように書けばいいかの例です。

machine:
    timezone:
        Asia/Tokyo

    services:
        - docker
dependencies:
    pre:
        - sudo pip install --upgrade awscli

deployment:
    production:
        branch: master
        commands:
            - ./deploy.sh $CIRCLE_SHA1

今回の話に関係のあるところだけ書きました。

deploymentにbranchをmasterに指定することでmasterブランチのCIが通った時にcommands以下が実行されます。Circle CI内ではGitHubのコミットIDが$CIRCLE_SHA1という変数に入っています。

aws-cliを使用するために$AWS_ACCESS_KEY_IDと$AWS_SECRET_ACCESS_KEYの環境変数を設定する必要がありますが、セキュリティ上の問題からバージョン管理に含めずにCircle CI上のUIから設定します。

deploy.sh

SHA1=$1
DOCKERRUN_FILE=$SHA1-Dockerrun.aws.json
EB_BUCKET= # Elastic Beanstalkのバージョンファイルが入るS3のバケット名

aws ecr get-login | bash

docker build -t {AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:$SHA1 .
docker push {AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:$SHA1

sed "s/<tag>/$SHA1/" < Dockerrun.aws.json > $DOCKERRUN_FILE

aws s3 cp $DOCKERRUN_FILE s3://$EB_BUCKET/$DOCKERRUN_FILE

aws elasticbeanstalk create-application-version\
  --application-name application \
  --version-label $SHA1 \
  --source-bundle S3Bucket=$EB_BUCKET,S3Key=$DOCKERRUN_FILE

Elastic Beanstalkのバージョンはただのjsonファイルで管理されているのであらかじめDockerrun.aws.jsonという名前で以下のファイルを用意しています。

{
    "AWSEBDockerrunVersion": "1",
    "Image" : {
        "Name" : "{AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:<tag>"
    },
    "Ports" : [{ "ContainerPort": "80" }]
}

やっていることの流れとしては

  • aws-cliでログイン
  • Dockerコンテナをビルド
  • Dockerコンテナをpush
  • バージョンファイルをS3に上げる
  • Elastic Beanstalkに新しいバージョンを作る

という感じです。 ここまで行えばElastic Beanstalkに新しいバージョンが作成されているのがわかると思います。

注意点

Circle CI用に適切な権限を持ったIAMを用意する必要があります。以下の操作ができるようにIAMを作りましょう。

  • S3のバージョンファイルが入るバケットへのアップロード
  • Docker Container Registryへのpush
  • Elastic Beanstalkのバージョンの作成

まとめ

簡単にデプロイの自動化ができそうなのがわかっていただけたと思います。弊社のような小規模のチームでの開発では自動化できるところは自動化を進めて効率を高める必要があります。 このようなモダンな仕組みもすべてアルバイトエンジニアによって構築されています。プログラミング初心者の方はこのような新しい仕組みを学ぶことができ、実力のある人はDevOpsをさらに磨き上げることもできる職場です。

PHP勉強会でLTしてきた話

10月のPHP勉強会@東京でLTをしてきたのでその話をしようと思います。

PHP勉強会の雰囲気

社内の勉強会には複数の会社に参加したことがあるのですが、社外でいろいろな人が来るような勉強会は初めてでした。

しかし、この勉強会は毎回半数近くが初参加で、毎回最初に全員で自己紹介するというとても馴染みやすい雰囲気でした。また、年齢も上から下まで色々な方がいましたし、女性の方も数人いらっしゃいました。

ちなみに学生は僕の他にもう一人いました。

LT

勉強会初参加なので当然LTも初めてでした。5分間の発表時間なので技術的な話題をしようと思うとかなり辛いです。最初はコードのスライドが5枚くらいあって5分じゃ無理だとなったので何回もリファクタリングしました。

以下が実際のスライドです。

評判

最初の方はテンションを上げてなんとか場を盛り上げられてた感があったのですが、いざコードが登場すると一気に反応が悪くなって失敗したなという感じでした。

きている人の層が初心者から上級者までさまざまなので、全員に受けるような話をしようと思うともっとシンプルにまとめる必要がありそうです。

一応2次会ではエンジニアの方に良かったと言ってもらえたのでホっとしました笑

まとめ

PHP勉強会@東京は初心者や勉強会初めての人でも参加しやすいので時間があれば是非参加してみるといいと思います。技術的な話を聞くだけでなく色々な人と出会えて、普段はできないようは話ができるのもいいところだと思います。次回の募集が始まっているので参加してみてはいかがでしょうか。

DockerでLaravelの開発環境を爆速で構築する

弊社では人材紹介会社様と人材派遣会社様向けのシステムの開発をしており、今年初めごろに派遣会社スタッフ様向けの新サービスであるWorkinGoodを始めました。

www.workingood.com

主にPHPを使用して開発していますが、新しいサービスに関してはAWSやDocker、Reactなどトレンドとなっているものに追随して技術を選択しています。

そこで今回は弊社の開発環境構築にDockerを採用した話をしたいと思います。

なぜDockerを導入したか

弊社ではプロダクションへのデプロイでもDockerを使用していますが、先ほどもお話ししたように弊社では複数のプロジェクトがあり、それぞれが異なるPHPのバージョンで動いています。このため毎回環境構築しようと思うと複数バージョンのPHPを入れる必要がありとても手間となってしまいます。

そこでDockerを使うことで気軽に環境を構築できるようにしました。

Dockerに関しては様々な記事が出ているのでここでは説明しませんが、今ではDocker ToolboxやDocker for Mac、Docker for Windowsのおかげで手軽に導入することができます。

どうやっているのか

ここではLaravelとMySQLの標準的な環境を想定します。

まずはLaravelのコンテナを作る必要があります。

FROM php:7.0.8-alpine

RUN apk --no-cache add curl nodejs python make g++ &&\
    npm install -g gulp &&\
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer &&\
    docker-php-ext-install pdo_mysql curl

WORKDIR /var/www/localhost/htdocs

EXPOSE 8000

CMD ["php", "-S", "0.0.0.0:8000", "-t", "/var/www/localhost/htdocs/public"]

alpine linuxという軽量なイメージをベースにしたPHP7のコンテナを使用しています。

最後にPHP付属の開発用サーバーを起動して終わりです。

MySQLは特にカスタマイズしないので公式のものをそのまま使います。

Dockerコンテナ立ち上げ

bashスクリプトを用意して一発で立ち上げます。

# 用意したDockerfileのビルド
docker build -t matchingood/application:dev .

# 現在のパスを取得
path=$(cd $(dirname $0) && pwd)

# MySQLの公式イメージ
docker pull mysql

# 各イメージを動かす
docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d mysql
docker run -p 8000:8000 \
    --link mysql:mysql \
    --name application \
    -v ${path}:/var/www/localhost/htdocs \ 
    -t -d matchingood/application:dev

# Laravelの設定ファイル
cp .env.example .env

# applicationとリンクしているMySQLのホストを取得
dbhost=$(docker exec application env | grep MYSQL_PORT_3306_TCP_ADDR | cut -d '=' -f 2-2)

# Laravel設定ファイルのMySQLの情報をコンテナ内から見えるホストに置換
sed -i -e "s/DB_HOST=127.0.0.1/DB_HOST=${dbhost}/g" .env

# MySQLコンテナにアプリケーション用のデータベースを作成
docker exec mysql mysql -u root --password=mysql -e "create database application;"

# Laravelを動かすために必要なものたち
docker exec application composer install
docker exec application php artisan key:generate
docker exec application php artisan migrate
docker exec application php artisan db:seed

これでもうLaravelの開発環境を整えることができました!

Docker Compose

LaravelのコンテナにMySQLとElasticSearchのコンテナをリンクさせるときにlinkオプションで指定しています。ですが、Docker Composeと言うものを使うことで綺麗にこのあたりのオーケストレーションを行うことができます。ただ今回はそこまで複雑な構成ではないのでbashスクリプトで書いています。

まとめ

実際の例を見て、Dockerで簡単に環境を構築できることが分かったと思います。

AWSLambdaの私的Tips

今回はSQSLoggerとその周辺のお話ということで、AWS LambdaについてのTipsを書こうかと思います。

※SQSLoggerは私が以前作成したLaravelフレームワーク上で動くログデータをSQSへプッシュするライブラリです。詳しくはこちらの記事をご覧ください。

www.wantedly.com

AWS Lambdaとは?

aws.amazon.com

AWS Lambdaは、サーバレスでコードを実行できるAWSのサービスです。実行できるコードは現在Node.js, Python2, Java8に限られていますが、特に環境の設定をせず気軽にコードが実行できる点が魅力です。

実行タイミングは、cronのような時間に基づいた定期実行もできますが、AWSの他のサービスが発したイベントをListenして実行することもできます!実行タイミングの指定はWebUI上ででき、手軽です。

料金体系は使用した時間に対する課金で、無料利用枠も十分にあるので「定期的にコードを実行したいけどEC2のインスタンス借りるのはちょっと…」っていう人にもおすすめです。

本題: Node.jsでLambda Functionを書くときのTIPS

自分がLambdaを使うときはjsを使うのですが、以前はLambdaに関する資料や実装例があまりなく、結構苦労しました。そこで、自分がLambdaに触る上で手に入れたTIPSを書き残しておこうと思います。ちなみに筆者の環境はUbuntu15.10ですが、Macならおおよそ流用できるかと思います。

1. aws-sdkはいらない!

Node.jsを用いて書く場合、npmライブラリを使う場合はindex.jsとnode_modulesディレクトリをzipしてアップロードする必要があります。しかし、もし使うライブラリがaws-sdkのみである場合は、必要ありません。

これは、LambdaFunctionが実行されるインスタンス上で、requireできるパス上にすでにaws-sdkが存在するためです。こうすることでデプロイの時間を少し短縮することができます。

2. zipするときはzip -r project.zip index.js node_modules

とはいえ、npmライブラリがどうしても使いたい時があるとおもいます。そういう時はindex.jsとnode_modulesの入ったprojectというディレクトリを以下のようにzipしたくなりますが… $ zip -r project.zip project このzipファイルをアップロードするとLambdaに'index.jsが見つからない'と怒られてしまいます。これはLambdaの仕様で、展開したディレクトリ上に直にindex.jsがないといけません。よって正しいコマンドは

$ cd project
$ zip -r project.zip index.js node_modules

になります。地味にハマるポイントではないでしょうか。

3. Nodeのバージョンは合わせておこう!

自分ではあまりはまったことがないのですが、普段node 6.x系使っているともしかしたら動かない可能性があるかもしれません。(とくにnaitive extensionを使っている場合とか)Lambdaの実行環境は2016/10/13現在では4.3なので、ローカルの環境を確認しておきましょう。

Nodeのバージョン管理といえば、rbenvやpyenvと同様にnodenvがありますが、個人的にはnpmでインストールできて気軽に使えるnがおすすめです。

$ npm install -g n
$ n 4.3.0

これだけで終わり!(Linux環境等では適宜sudoしてinstallしてください)

4. ハンドラに渡される引数を有効に使おう!

Lambda関数を書く時、雛形ファイルとして与えられる書式には、関数の引数として3つの変数が用いられています。

exports.handler = (event, context, callback) => {
    .
    .
    .
};

これらの引数はそれぞれ実行にあたって有効な情報を与えてくれます。

eventは言わずもがな、Lambda関数を発火したイベントの詳細情報が入ってきます。たとえば、S3上のファイル作成がトリガーになったときには、作成されたファイルのkeyやbucketを始めとした様々な情報が入ってきます。

  "Records": [
    {
      "eventVersion": "2.0",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "s3": {
        "configurationId": "testConfigRule",
        "object": {
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901",
          "key": "HappyFace.jpg",
          "size": 1024
        },
        "bucket": {
          "arn": bucketarn,
          "name": "sourcebucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          }
        },
        "s3SchemaVersion": "1.0"
      },
      "responseElements": {
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH",
        "x-amz-request-id": "EXAMPLE123456789"
      },
      "awsRegion": "us-east-1",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "eventSource": "aws:s3"
    }
  ]
}

contextにはさまざまな実行時情報が入ってきます。特によく使うのはgetRemainingTimeInMillisで、呼び出した段階での残りコンピュート時間をミリ秒で返してくれます。これをつかえば、残り時間が5秒をきったら処理をやめる、というようなことができます。なにかをループするような場合には以下のようにwhileの条件として使います。

while (context.getRemainingTimeInMillis() > 5000) {
    hogehoge();
    .
    .
    .
}

callbackは最近Lambdaのnodeのバージョンが上がって初めて追加されたもので、関数の実行結果を呼び出し元に返すのに用います。第一引数には関数が失敗した時の実行結果を表し、第二引数は正常終了した場合に用います。

callback(Error error, Object result);

使用は任意なので無理に使うことはないですが、AWS上でデバッグする必要があるときは重宝します。

まとめ

以上、Lambdaを使う上で気になるTIPSをいくつか書かせていただきました。LambdaはEC2やRDSなどの基幹サービスに比べれば迫力は落ちますが、応用できる幅が非常に広いサービスです!イベントが発生した時にSlack等になにか軽いNotificationを送りたい場合や、同じくAWSのサービスであるAPI Gatewayを使ってステートレスなWebAPIを作成できる(!!)など、既にたくさんの応用例があります!

もしあなたがなにかサーバー上でちょっとしたことをやることになったら、まずはLambda上でできるかを考えてみませんか?

私達の会社では、AWS上の様々サービスを使ってWebアプリケーションをより効率的かつスケーラブルに実装することが今ホットな話題の一つになっています。

エンジニアが職場環境をモダンにした話

以前の職場環境

僕がマッチングッドで働き始めてからちょうど2年経ちました。

入った直後に使われていたツールや開発環境は以下のような感じでした。

最初はWebについての知識はほとんど持っていなかったので、こんなものかなと思っていたのですが、自分で情報を集めるようになるうちにこの環境はもう時代遅れなのだということがわかるようになりました。

会社の環境を大きく変える転機

入社してから1年が経つ頃に、新規のシステムを自分たちで作るチャンスがやってきました。これは現在稼働しているWorkinGoodというサービスです。

www.workingood.com

これは一から作るなら全て最新のものを使って作ろうと決めて作り始めたので、開発しているチームでは技術だけでなくツールやフローも含めて最新を目指した結果以下のようになりました。

  • Slack
  • GitHub
  • React
  • TypeScript
  • Laravel
  • AWS
  • Docker
  • Circle CI
  • GitHub Workflow

先ほどと同じ会社とは思えないほどの変化です。

SkypeからSlackへの変化は単にサービスの使い勝手がよくなっただけでなく、ボットと連携することができるのでいくつかのオペレーションをボットから行うことができるようになったり、天気を教えてくれたり、チャットツールに止まらない可能性を秘めていることを実感しました。

開発環境で最も変わったのはインフラ周りだと思います。今まで専用のスクリプトでパッケージ化して配布していたリリースフローを、CIとDockerとAWSによってデプロイを自動化したことにより少人数でも安全にオペレーションできるようになりました。

会社全体に広めた

この開発チームでの環境がとても良かったので、会社全体に広げることにしました。

コミュニケーションの変化

今までSkypeとメールでしていたやり取りは全てSlackで行うようにしたことでコミュニケーションが円滑になっただけではなく、チャンネル機能を駆使して今まで以上に社員の親密度が上がった気がします。

フローの変化

また、今まで明確な開発フローがなく、masterブランチに直pushしていたのをGitHub Workflowをベースにした独自のフローを定めることにより、安全にコードを管理、開発できるようになりました。

共有の変化

最も大きな変化は、SkypeからSlackへ移ったことと、エンジニアのタスク管理をAsanaからGitHubのissue に移したことで他の社員が今何をやっているのかがわかるようになったことです。個人的にこれが最大のポジティブな影響を与えていると思います。

他の人が今何をやっているのかがわかると、プロジェクト全体の見通しをつけることができるだけではなく、自分自身のモチベーションに繋がります。他の人がすごいことをやっていれば、自分も負けじと頑張れる人にはとてもいい環境です。

まとめ

ツールや環境が新しくなることで生産性の向上だけでなく雰囲気までよくなった(当社比)気がします。

まず身内で使ってみて、それが良いものであるとわかったなら是非上司を説得して広めてみてください。エンジニアにとって開発環境とはカメラマンにとってのカメラであり、画家にとっての筆と絵の具です。

そこを妥協してしまったらいいものができるはずがありません。会社を次のステップへ行かせようと思ったらモノを作っているエンジニアが職場を変えるべきだと思います。