読者です 読者をやめる 読者になる 読者になる

LIVESENSE made*

リブセンスのエンジニアやデザイナーの活動や注目していることをまとめたブログです。

MENU

Livesense式 開発合宿マニュアル part1

開発合宿

f:id:taise:20161112151149j:plain

Analyticsグループの大政です。
普段はデータ分析基盤や、レコメンド・エンジンの開発をしています。

開発合宿、やっていますか?
Livesenseでは、仕事の一環で開発合宿をすることもあれば、有志で集まって週末に開発合宿をすることもあります。
これまで5回ほど有志で開発合宿を行ってきた経験を元に、開発合宿を成功させるためのノウハウをお話したいと思います。

今回は、(1)なぜ開発合宿をやるのか、(2)どんなスタイルがあるのか、(3)おすすめの施設はどこかについてご紹介したいと思います。

続きを読む

レガシーコードの最適化とPHPバージョンアップ

PHP 開発 運用

f:id:boscoworks:20161021132156p:plain

 少し前のことになりますが、正社員転職サービス「ジョブセンスリンク」を構成するPHPアプリケーション群のPHPバージョンアップ対応と、それに合わせてレガシーコードの大幅な整理を行いました。
 「PHPのバージョンあげて、リファクタリングしたんだ」と一言で言えば簡単ですが、日々のサービス改善を滞らせず、システムのリニューアルを同時に進めていくのは多大な労力を要しました。
 今回はその仕事を主に担当した、キャリア事業部技術基盤チーム*1の海野がお届けします。お手柔らかにどうぞ。

ミッション

 PHPのバージョン問題。レガシーコードの山積。システムが歳を重ねるにつれ、必ず直面する大きな問題です。
 システムは、初めてリリースされた数年前の数倍の規模になっているでしょう。
 売上を支えるシステムを維持し、事業を加速させる施策を阻むこと無く、システムのリニューアルを進める。これが今回のミッションとなりました。
 このミッションを進める上で解決したかったのは、以下の2点です。

  • PHPバージョンアップ
  • 全社共有ライブラリの最適化

 今回は、この課題に対する取り組みについてお話します。

*1:ジョブセンスリンクのインフラ管理やシステム開発を主に担当しています

続きを読む

ReduxにおけるGlobal stateとLocal stateの共存

フロントエンド Redux 開発 React.js

初めまして!エンジニアの米山と申します。 転職会議ではフロントエンド開発にReact.jsとReduxを利用しています。 今回はReact, Redux開発におけるGlobal stateLocal stateという考え方について、軽く紹介させていただきます。

Redux開発の難点

ReduxはSingle source of truthという原則を採用しており、アプリケーションの状態は1つのオブジェクトに格納されます。それゆえ、アプリケーションの状態が散らばることなく管理が楽になります。

ただし、その弊害としてstateが肥大化します。stateが肥大化すると、reducerが肥大化する可能性が高まります。

対応策としては、reducerを分割したりNormalizrのような便利なツールを使う方法が考えられます。

しかし、React自身が提供するState管理を併用することで、Reduxの責務範囲をより適切化できる可能性があります。そして結果的にstateの肥大化を多少は抑制できるかもしれません。

それがGlobal stateとLocal stateの共存です。

Global stateとLocal state

今回の記事は以下のディスカッションを参考にさせていただきました。

Redux and global state vs. local state

Global state, Local stateという考え方は上記のディスカッションで使われているものであり、この記事はその紹介、という形になります。その上で事例を作成してみて、所感を載せています。

気になる方はぜひ元記事をご覧ください。

Global stateとは

Global stateは単にReduxの管理下に置かれるstateです。

どこからでも参照できるという意味でのGlobalではなく、アプリケーションレベルで管理される程度の意味合いですね。

Local stateとは

一方でGlobal stateではない(Reduxの管理外であるような)stateをLocal stateと呼びます。

最もカンタンな例は、React.Componenrtを継承したクラスを使って管理される状態でしょう。

Local stateの例

Local stateの例としては、コンポーネントに閉じるような状態が挙げられるかなと思います。

たとえば、あるコンポーネントがクリックされた回数を考えます。もしこのクリック回数という状態が、コンポーネントの中でしか使われない(たとえばrender関数内で表示するのみ)としたら、Reduxで管理せずとも良さそうです。

逆に言うと、クリック回数がアプリケーション全体に影響するならReduxに任せるべきかもしれません。

実例で見るGlobal stateとLocal stateの共存

では、ここからはGlobal stateとLocal stateの共存方法について、実例を見ていきたいと思います。

前提環境

まずは、React + Reduxによるアプリケーション作成環境を整えます。

お試しに使う程度なので、こちらのボイラープレートを使うと良いでしょう。

TrySpace/simple-redux-boilerplate

$ git clone git@github.com:TrySpace/simple-redux-boilerplate.git

内容はシンプルなカウンタアプリケーションで、最低限のRedux stateフローが作成されています。

$ git cloneが終わったら、ディレクトリをリネームしておくと良いでしょう。

$ mv simple-redux-boilerplate local-state-trial

パッケージをインストールして、npm startコマンドを打てば、開発に入ることができます。

$ cd local-state-trial
$ npm i
$ npm start

http://localhost:3000/ にアクセスすると、以下のようなアプリケーションが立ち上がります。

f:id:livesense-made:20160926162230p:plain

マイナスあるいはプラスのボタンをクリックすると、カウンタがデクリメントあるいはインクリメントされます。

Global state

Global stateについては、今回は既に作成済です。

// ./src/reducers/counter.js
export default function counter(state = 0, action) {
  switch (action.type) {
    case INCREMENT_COUNTER:
      return state + 1;
    case DECREMENT_COUNTER:
      return state - 1;
    default:
      return state;
  }
}

カウンタの状態に関して、Reduxが管理を行っています。なのでpropsを通じて、他のコンポーネントから現在のカウント数を参照することができます(Local stateの場合は、その子コンポーネントからのみ参照可能)。

Local state

では、Local stateを作成していきましょう。 ここでは、カウンタの表示・非表示を切り替える機能を付けてみます。表示であるか非表示であるかがここでのstateですね。

こちらが現在のカウンタコンポーネントです。

import React, { Component, PropTypes } from 'react';

export default class Counter extends Component {
  constructor(props, context) {
    super(props, context)
  }

  handleIncrement() {
    this.props.actions.increment()
  }

  handleDecrement() {
    this.props.actions.decrement()
  }

  render() {
    return (
      <div className="counter-container">
        <div className="counter-num-label">{this.props.counter}</div>
        <div className="counter-even-label">{this.props.counter % 2 === 0 ? 'even' : 'odd'}</div>
        <br />
        <div className="counter-buttons">
          <button onClick={() => {this.handleDecrement();}}>-</button>
          <button onClick={() => {this.handleIncrement();}}>+</button>
        </div>
      </div>
    )
  }
}

Counter.propTypes = {
  counter: PropTypes.number.isRequired,
  actions: PropTypes.object.isRequired
}

まあ、普通のReactコンポーネントですね。

ちなみに、Koba04さんの「Reactの最新動向とベストプラクティス」によりますと、最近では状態を持たないコンポーネントはStatelessFunctionalComponent(ステートレス ファンクショナル コンポーネント)を使うと良いらしいです。

import React from 'react'

const Hoge = (props) => {
  return (
    <div>
      {props.hoge.body}
    </div>
  )
}

export default Hoge

そしてもし、コンポーネントが状態を持つようになったら、React.componentを継承したclassを使おうねとのこと。

import React from 'react'

class Hoge extends React.Component {
  constructor(props, context) {
    super(props, context)
    this.state = {
      hoge: {
        body: '本文'
      }
    }
  }

  render() {
    return (
      <div>
        {this.state.hoge.body}
      </div>
    )
  }
}

なので、Local stateを扱うには、StatelessFunctionalComponentではなく、React.Componentを継承したクラスを用います。

では、先程のカウンタコンポーネントに、表示・非表示のLocal stateを持たせてみます。

export default class Counter extends Component {
  constructor(props, context) {
    super(props, context)

    // Local stateを定義
    this.state = {
      isVisible: true
    }
  }

  // 表示・非表示の切り替えアクション
  toggleVisibility() {
    this.setState({
      // 表示・非表示を反転
      isVisible: !this.state.isVisible
    })
  }

  handleIncrement() {
    this.props.actions.increment()
  }

  handleDecrement() {
    this.props.actions.decrement()
  }

  render() {
    // 表示 or 非表示
    const isVisible = this.state.isVisible ? 'block' : 'none'
    return (
      <div className="counter-container">
        {/* ↓ 表示・非表示stateに応じて、CSSで表示を操作 */}
        <div className="counter-num-label"  style={{display: isVisible}}>{this.props.counter}</div>
        <div className="counter-even-label">{this.props.counter % 2 === 0 ? 'even' : 'odd'}</div>
        <br />
        <div className="counter-buttons">
          <button onClick={() => {this.handleDecrement();}}>-</button>
          <button onClick={() => {this.handleIncrement();}}>+</button>
        </div>
        
        {/* 表示・非表示の切り替えアクションを発行するボタン */}
        <button onClick={() => {this.toggleVisibility() }}>
          {this.state.isVisible ? '閉じる' : '開く'}
        </button>
      </div>
    )
  }
}

コード中にコメントを書きましたが、以下、個別に見ていきます。

Local state定義

contractor 関数内でLocal stateを定義します。

constructor(props, context) {
  super(props, context)

  // Local stateを定義
  this.state = {
    isVisible: true
  }
}

これはReact, Reduxというより、JavaScriptのclassの話ですね。class内から、this.stateの形で参照することができます。

参照: MDN - class

Local state変更アクションの定義

Local stateを変更するアクションを定義します。

// 表示・非表示の切り替えアクション
toggleVisibility() {
  this.setState({
    // 表示・非表示を反転
    isVisible: !this.state.isVisible
  })
}

toggleVisibilityが呼ばれると、setStateが行われてカウンタコンポーネントが再レンダリングされます。

参照: Component API - setState

Local stateをrender関数内で利用

class内で定義したLocal stateとactionをrender関数内で利用します。

render() {
  // 表示 or 非表示
  const isVisible = this.state.isVisible ? 'block' : 'none'
  return (
    <div className="counter-container">
      {/* ↓ 表示・非表示stateに応じて、CSSで表示を操作 */}
      <div className="counter-num-label"  style={{display: isVisible}}>{this.props.counter}</div>
      <div className="counter-even-label">{this.props.counter % 2 === 0 ? 'even' : 'odd'}</div>
      <br />
      <div className="counter-buttons">
        <button onClick={() => {this.handleDecrement();}}>-</button>
        <button onClick={() => {this.handleIncrement();}}>+</button>
      </div>
      
      {/* 表示・非表示の切り替えアクションを発行するボタン */}
      <button onClick={() => {this.toggleVisibility() }}>
        {this.state.isVisible ? '閉じる' : '開く'}
      </button>
    </div>
  )
}

コードについては以上です。 コンポーネント内に閉じたstate及び、actionを作成することができました。要は素のReactの世界ですね。これがReduxのGlobal stateと共存しています。ポイントは、お互いが干渉すること無く共存している点ですね。

挙動

きちんとGlobal state(カウンタのインクリメント・デクリメント)と、Local state(カウンタの表示・非表示)が共存しているのがわかります。

f:id:livesense-made:20160926162321g:plain

状態管理をRedux外に漏らすことについて

Reduxの公式FAQに、以下のような項目があります。

Do I have to put all my state into Redux? Should I ever use React's setState()?

アプリケーションの状態管理を全てのReduxに任せるべきか、あるいはReactのsetStateも使っていくべきなのか。というものです。

返答としては、以下の通り。

There is no “right” answer for this. (中略) Find a balance that works for you, and go with it.

正しい答えはないので、良い塩梅を自分で探ってみてね。とのこと。 Reduxサイドとしても「全ての状態管理をReduxで行うべき」とは考えていないようです。

2016/09/29 追記:

Reduxの作者のDan AbramovがReduxの用法についてMedium.comに記事を投稿していました。

You Might Not Need Redux - Medium.com

Reduxを使わずともReduxのアイデアを取り入れることはできるし、 Local state is fine. とのことです。

Local stateを実現するツール

先程のリンクの中で、Local stateを実現するためのツールがいくつか挙げられています。

ただしどれもdecoratorを使う必要があるので、慣れていない人は今回紹介したような手法でもいいのかなと思います。

Local stateを使うべき時

では、どんなときにLocal stateを使うべきなのでしょうか。 個人的には、UIに関わる状態はLocal stateに向いていそうだなと思います。例としては、

  • 上下キーによるセレクトボックスのカーソル移動
  • レビューの評点スターhoverイベント

などが、コンポーネントに閉じたstateとして適していそうです。以下は、転職会議のUIイベントをスクショしたものです。

f:id:livesense-made:20160926162444g:plain

https://jobtalk.jp/

これはUI上の描画に関わる状態であり、Local stateとしてコンポーネント内で利用するのが良いでしょう。

一方で、ドメインやビジネスロジックに関わる状態は、ReduxでGlobal stateとして管理するのが良さそうです。

また、Local stateとして定義したものが、実はGlobal stateとして定義するべきだった…ということもあるかもしれません。状態がGlobalで持つべきものなのかLocalで持つべきものかについては、チーム内で話し合うと良いかもしれません。

締め

今回はReduxでGlobal stateとLocal stateを共存させる方法について書かせて頂きました。

Global state, Local stateと偉そうな名前を使いましたが、要はReduxと素のReactを一緒に使っているだけですね。ただし、Redux初心者の自分としてはなるほどなあという気持ちでした。

適宜Local stateを用いることで、Reduxの難点の1つであるState, Reducerの肥大化を抑制できるかもしれませんね。 React・Reduxアプリケーションを作成する上で、参考になりましたら幸いです。

Terraform & Packer での運用におけるサーバの構成変更

AWS Insfrastructure as Code Terraform インフラ 開発 運用 Packer

こんにちは、エンジニアの野本です。先日、door 賃貸をオンプレから AWS に移行した際、Terraform & Packer を中心に行ったという話を紹介しました。サーバの構成変更が必要な場合は Packer & Ansible で AMI を再作成、Terraform にて入れ替えるといういわゆる Immutable Infrastructure な運用をしているのですが、基本的には無停止でオンラインで入れ替える必要があり、それを行うのに工夫している部分をご紹介します。

Packer / Terraform による構成管理

Packer による AMI の作成

Packer の build にて、Ansible を利用して以下のようなものをプロビジョニングするようにします。

  • 各種ミドルウェアとその基本的な設定
  • 監視 / ログ収集 / デプロイのエージェント

この際、ステージング環境や本番環境に依存するような設定は、実際の起動時に cloud-init で設定を注入出来るように設定ファイルのテンプレートを配置しておくなどの対応をして、その環境の差分をなくすように構築するようにしておきます。

Terraform でのインスタンス起動時の user-data の利用

Packer にて作成した AMI を元にインスタンスを起動しますが、先の Packer のところで説明したように、環境が決まって初めて決定される設定項目は cloud-init を利用して設定を行います。 cloud-init で実行する基本的な user-data を template_file (0.7 以降であれば data として) で作成、Terraform の実行時に環境毎の vars で設定してある内容で user-data を生成するようにします。

このように Packer と Terraform の設定を行い、ステージングでも本番環境に対して行うのとほぼ同等のオペレーションを実施 / 検証出来るようになっています。

Terraform でのサーバの入れ替えの為の設定 / 作業

Auto Scaling グループに対する ELB 付け外しの利用

現在の Auto Scaling グループは ELB の付け外しが出来るようになっているので、これを利用します。Auto Scaling は blue / green の2系統を用意、ELB も本番用 / 確認用の2つを用意しています。

基本的な Terraform の設定はこちら。各設定は説明のために簡略化されてます。

autoscale.tf
resource "aws_launch_configuration" "blue" {
  image_id = "${var.blue_ami_id}"
  instance_type = "${var.blue_instance_type}"
  user_data = "${template_file.user_data_blue.rendered}"
  ...
}

resource "aws_autoscaling_group" "blue" {
  launch_configuration = "${aws_launch_configuration.blue.id}"
  min_size = "${var.blue_min_size}"
  max_size = "${var.blue_max_size}"
  load_balancers = ["${var.blue_elb_name}"]
  ...
}

resource "aws_launch_configuration" "green" {
  image_id = "${var.green_ami_id}"
  instance_type = "${var.green_instance_type}"
  user_data = "${template_file.user_data_green.rendered}"
  ...
}

resource "aws_autoscaling_group" "green" {
  launch_configuration = "${aws_launch_configuration.green.id}"
  min_size = "${var.green_min_size}"
  max_size = "${var.green_max_size}"
  load_balancers = ["${var.green_elb_name}"]
  ...
}
elb.tf
resource "aws_elb" "prd" {
    name = "elb_prd"
    ...
}

resource "aws_elb" "test" {
    name = "elb_test"
    ...
}
codedeploy.tf
resource "aws_codedeploy_app" "app" {
  name = "app"
}

resource "aws_codedeploy_deployment_group" "app" {
  app_name = "${aws_codedeploy_app.app.name}"
  deployment_group_name = "app"
  autoscaling_groups = [
    "${aws_autoscaling_group.blue.id}",
    "${aws_autoscaling_group.green.id}"
  ]
  ...
}
variables.tfvars
blue_ami_id = "ami-12345xyz"
blue_min_size = "2"
blue_max_size = "4"
blue_elb_name = "elb_prd"

green_ami_id = "ami-12345xyz"
green_min_size = "0"
green_max_size = "0"
green_elb_name = "elb_test"

参考にしたものはこちら

また、今回は blue green deploy の話ではないのですが、CodeDeploy を利用して blue / green のAuto Scaling ともに同じ deployment group を設定しておくと、新しく構築した系統のサーバにアプリケーションが自動的にデプロイされて便利です。

実際のオペレーションの手順

前述のように、同一の AMI でステージングでその AMI の検証と Terraform でのオペレーションの確認を行えるようになっています。なので、以降の手順は基本的にはステージングで全く同じ手順で確認作業を行い、問題がなければまた本番で同じ手順で作業を行うという運用を行っています。 ここでは、blue が稼動中の以下のような状態からどのような動きになるのか説明していきます。

f:id:livesense-made:20160905130036p:plain

各手順においての主な作業は、各手順の下部に記載している variables.tfvars の変数を変更し terraform apply で適応するというものになります。

1. green の設定変更 / 起動

green の台数を指定して、green サーバを起動します。この際、Auto Scaling と Code Deploy の連携により前回成功時のアプリケーションが自動的に green にデプロイされます。

この時はまだ本番の ELB ではなく確認用の ELB にぶら下がっています。起動とデプロイノ完了を確認したら確認用の ELB に対して動作確認を行います。

f:id:livesense-made:20160905130048p:plain

# variables.tfvars

blue_ami_id = "ami-12345xyz"
blue_min_size = "2"
blue_max_size = "4"
blue_elb_name = "elb_prd"

green_ami_id = "ami-98765abc"       # 新しい AMI を設定
green_min_size = "2"                # AutoScale の台数を設定し EC2 を起動
green_max_size = "4"                # 同上
green_elb_name = "elb_test"         # 確認用の ELB を設定
2. green サーバ群を本番 ELB に設定

正常に動作することが確認出来たら、green サーバ群を本番 ELB に設定します。ここでも Terraform の変数化した ELB の設定箇所を変更し apply します。完了したら green サーバ群でエラーが起っていないか確認を行います。

f:id:livesense-made:20160905130100p:plain

# variables.tfvars

blue_ami_id = "ami-12345xyz"
blue_min_size = "2"
blue_max_size = "4"
blue_elb_name = "elb_prd"

green_ami_id = "ami-98765abc"
green_min_size = "2"
green_max_size = "4"
green_elb_name = "elb_prd"       # ELB を本番に
3. blue サーバを本番 ELB から切り離す

エラーもなく問題がなければ blue サーバ群を本番 ELB から切り離します。green サーバのみになった後もエラーが起きていないかを確認。問題があれば blue をまた ELB に戻すよう設定を行います。

f:id:livesense-made:20160905130112p:plain

# variables.tfvars

blue_ami_id = "ami-12345xyz"
blue_min_size = "2"
blue_max_size = "4"
blue_elb_name = ""              # blue の ELB を無効に

green_ami_id = "ami-98765abc"
green_min_size = "2"
green_max_size = "4"
green_elb_name = "elb_prd"
4. blue の台数を 0 に

green サーバ群のみで問題がなければ移行は無事完了ということで blue サーバ群を退役させます。

以降は green サーバ群が稼働中となり、次回入れ替え時は blue を green に置き替えて 1. からまた作業を実施します。

f:id:livesense-made:20160905130122p:plain

# variables.tfvars
blue_ami_id = "ami-12345xyz"
blue_min_size = "0"             # EC2 を起動しないように
blue_max_size = "0"             # 同上
blue_elb_name = ""

green_ami_id = "ami-98765abc"
green_min_size = "2"
green_max_size = "4"
green_elb_name = "elb_prd"

実際に運用してみて

  • Terraform の変数と AWS の各種機能の連携で導入はさほど難しくない
  • ステージング / 本番で全く同様の手順で操作出来る環境を作っておくことで、作成した AMI のテストや Terraform の挙動などの確認も行える
  • 段階を踏んで入れ替えることでより安全に作業を行える

課題や今後

  • terraform apply する回数が多い / 自動化しづらい

    • 各手順において確認を行いたいので今のところ無理に自動化はしていないがやはり若干の手間は感じている
  • blue green 'deployment'

    • 本サイトのデプロイ頻度などの要因を顧み、平常時のデプロイにおいて blue / green のデプロイは現在行っていない
    • blue / green のデプロイを行える基盤は出来ているので、大幅な改修のデプロイ時には活用出来る
  • Packer build の時間がかかる

まとめ

この仕組みで移行後しばらく運用し、実際に入れ替え作業も何度も行っていますが、問題なく行いたい作業は実現出来ています。カジュアルにサーバを捨てたり入れ替えたり出来る仕組みがあるのは、何か問題があった際は準備してあるサーバを入れ替えてしまえば良いという運用が出来るので、心理的に大分楽になったと思います。

また、クラスメソッドさんのこちらの記事でも同様のオペレーションを実現しており、大変参考になると思います。

以上 Terraform & Packer を使った運用の一部を紹介させていただきました。何かのご参考になればと思います。

チームによる継続運用を意識したAWS環境におけるTerraformの活用

AWS 開発 Terraform Insfrastructure as Code インフラ
  • 概要
  • 背景
    • 複数人数で一つの環境をコードで管理する場合の移行期と運用期の特性
      • 移行期
      • 運用期
  • Terraformの採用理由
  • 実際の運用
    • ディレクトリ構成
    • stateファイルの配置
  • 環境の定義
    • tfvarsによる切り替え
    • 環境固有のリソース定義
  • GitHubのPRフロー
  • よかったこと・課題
    • よかったこと
    • 課題

概要

どうも。篠田です。

「特定の"インフラ担当"・"開発メンバー"」や「古の記憶」に頼らず、『開発メンバー全員が拡張や移行作業を気軽にできるインフラ』を実現するために、私のチームで採用しているTerraformを使ったAWS環境運用フローをご紹介いたします。

Terraformで移行および運用するフローにしたことで、構成全体に対する変更の柔軟性が高まり、コードがあることで運用および拡張期において設計の変更や手戻りを恐れずに開発を進められるようになりました。

次は概要図です。

f:id:livesense-made:20160711112710p:plain

続きを読む