AWS ECSにてカスタムしたredmineのdockerイメージを動かすまでのメモ(その1)

redmineの構築、プラグインの導入をふつーにやると面倒くさい。
あと、一旦構築した後redmineのバージョンをあげるのもやっぱり面倒くさい。

→ので、dockerにてプラグインのインストールやらなにやらを手順をコード化して簡単にRedmineの導入が
できるようにしました。
なお動作環境はAWSのECS(Elastic Container Service)を使います。

大きな流れ

1.Dockerfileを用意
2.AWSにてElastic Container Service(ECS)のタスクを定義
3.ECSのインスタンスを用意

今回はまず1を用意します。

1.Dockerfileを用意

redmineの公式イメージがdockerhubにあるのでこれをもとにpluginを導入する手順を
dockerfile化していきます。

ポイントは2つです。
1.ベースのイメージはredmine:x.x.x-passengerを利用する
2.DBのマイグレーションが必要ないものはpluginsフォルダに配置し、マイグレーションが必要なものは別フォルダ(install_plugins)に配置。
→コンテナ起動時にマイグレーションを行うようdocker-entrypoint.shに記載しておきます。

インストールするプラグイン一覧

独断と偏見で入れたプラグインです。

No プラグインの名前 概要
1 gitmike githubの雰囲気のデザインテーマ
2 backlogs スクラム開発でおなじみ。ストーリーボード
3 redmine_github_hook redmineとgitを連動
4 redmine Information Plugin redmineの情報を表示可能
5 redmine Good Job plugin チケット完了したら「Good Job」が表示
6 redmine_local_avatars アイコンのアバター
7 redmine_startpage plugin 初期ページをカスタマイズ
8 clipboard_image_paste クリップボードから画像を添付できる 
9 Google Analytics Plugin 閲覧PV測定用
10 redmine_absolute_dates Plugin 日付を「XX日前」 ではなくyyyy/mm/ddで表示してくれる
11 sidebar_hide Plugin サイドバーを隠せる
12 redmine_pivot_table ピボットテーブルできる画面追加
13 redmine-slack 指定したslack channnelに通知可能
14 redmine Issue Templates チケットのテンプレート
15 redmine Default Custom Query 一覧表示時のデフォルト絞り込みが可能に
16 redmine Lightbox Plugin 2 添付画像をプレビューできる
17 redmine_banner Plugin 画面上にお知らせを出せます
18 redmine_dmsf Plugin フォルダで文書管理できる
19 redmine_omniauth_google Plugin googleアカウントで認証可能
20 redmine view customize Plugin 画面カスタマイズがコードで可能

Dockerfile

上記のプラグインをインストール済みとするためのDockerfileです。
なお、ベースイメージは最新(2017/05/19時点)の3.3.3を使用しています。

Dockerfile
FROM redmine:3.3.3-passenger
MAINTAINER xxxxxxxxxxxxxxxxx

#必要コマンドのインストール
RUN apt-get update -y 
 && apt-get install -y curl unzip ruby ruby-dev cpp gcc libxml2 libxml2-dev 
  libxslt1-dev g++ git make xz-utils xapian-omega libxapian-dev xpdf 
  xpdf-utils antiword catdoc libwpd-tools libwps-tools gzip unrtf 
  catdvi djview djview3 uuid uuid-dev 
 && apt-get clean

#timezoneの変更(日本時間)
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ENV RAILS_ENV production

#gitmakeテーマのインストール
RUN cd /usr/src/redmine/public/themes 
 && git clone https://github.com/makotokw/redmine-theme-gitmike.git gitmike 
 && chown -R redmine.redmine /usr/src/redmine/public/themes/gitmike



#redmine_github_hookのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/koppen/redmine_github_hook.git

#Redmine Information Pluginのインストール
RUN curl http://iij.dl.osdn.jp/rp-information/57155/rp-information-1.0.2.zip > /usr/src/redmine/plugins/rp-information-1.0.2.zip 
 && unzip /usr/src/redmine/plugins/rp-information-1.0.2.zip -d /usr/src/redmine/plugins/ 
 && rm -f /usr/src/redmine/plugins/rp-information-1.0.2.zip

#Redmine Good Job pluginのインストール
RUN curl -L https://bitbucket.org/changeworld/redmine_good_job/downloads/redmine_good_job-0.0.1.1.zip > /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip 
 && unzip /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip -d /usr/src/redmine/plugins/redmine_good_job 
 && rm -rf /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip

#redmine_startpage pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/txinto/redmine_startpage.git

#Redmine Lightbox Plugin 2 Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/peclik/clipboard_image_paste.git

#Google Analytics Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/paginagmbh/redmine-google-analytics-plugin.git google_analytics_plugin

#redmine_absolute_dates Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/suer/redmine_absolute_dates

#sidebar_hide Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/bdemirkir/sidebar_hide.git

#redmine_pivot_tableのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/deecay/redmine_pivot_table.git

#redmine-slackのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack

#Redmine Issue Templates Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_issue_templates.git redmine_issue_templates

#Redmine Default Custom Query Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/hidakatsuya/redmine_default_custom_query.git redmine_default_custom_query

#Redmine Lightbox Plugin 2 Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/paginagmbh/redmine_lightbox2.git redmine_lightbox2

#redmine_banner Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_banner.git redmine_banner

#redmine_dmsf Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/danmunn/redmine_dmsf.git redmine_dmsf

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/yamamanx/redmine_omniauth_google.git redmine_omniauth_google

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/onozaty/redmine-view-customize.git view_customize

#redmine_local_avatars用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/ncoders/redmine_local_avatars.git

#backlogsのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN mkdir /usr/src/redmine/install_plugins 
 && cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/AlexDAlexeev/redmine_backlogs.git 
 && cd /usr/src/redmine/install_plugins/redmine_backlogs/ 
 && sed -i -e '11,17d' Gemfile

#database.ymlファイルを置くフォルダを用意
RUN mkdir /config
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 3000
CMD ["passenger", "start"]

docker-entrypoint.sh

次にdocker-entrypoint.shファイルです。
githubに公開されているファイル
(https://github.com/docker-library/redmine/blob/41c44367d9c1996a587e2bcc9462e4794f533c15/3.3/docker-entrypoint.sh)
を元にプラグインのインストールを行うコードを記載していきます。

docker-entrypoint.sh
#!/bin/bash
set -e

case "$1" in
    rails|rake|passenger)
        if [ -e '/config/database.yml' ]; then
                    if [ ! -f './config/database.yml' ]; then
                echo "use external database.uml file"
                ln -s /config/database.yml /usr/src/redmine/config/database.yml
            fi
        fi
                if [ -e '/config/configuration.yml' ]; then
                        if [ ! -f './config/configuration.yml' ]; then
                                echo "use external configuration.uml file"
                                ln -s /config/configuration.yml /usr/src/redmine/config/configuration.yml
                        fi
                fi
        if [ ! -f './config/database.yml' ]; then
            if [ "$MYSQL_PORT_3306_TCP" ]; then
                adapter='mysql2'
                host='mysql'
                port="${MYSQL_PORT_3306_TCP_PORT:-3306}"
                username="${MYSQL_ENV_MYSQL_USER:-root}"
                password="${MYSQL_ENV_MYSQL_PASSWORD:-$MYSQL_ENV_MYSQL_ROOT_PASSWORD}"
                database="${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}"
                encoding=
            elif [ "$POSTGRES_PORT_5432_TCP" ]; then
                adapter='postgresql'
                host='postgres'
                port="${POSTGRES_PORT_5432_TCP_PORT:-5432}"
                username="${POSTGRES_ENV_POSTGRES_USER:-postgres}"
                password="${POSTGRES_ENV_POSTGRES_PASSWORD}"
                database="${POSTGRES_ENV_POSTGRES_DB:-$username}"
                encoding=utf8
            else
                echo >&2 'warning: missing MYSQL_PORT_3306_TCP or POSTGRES_PORT_5432_TCP environment variables'
                echo >&2 '  Did you forget to --link some_mysql_container:mysql or some-postgres:postgres?'
                echo >&2
                echo >&2 '*** Using sqlite3 as fallback. ***'

                adapter='sqlite3'
                host='localhost'
                username='redmine'
                database='sqlite/redmine.db'
                encoding=utf8

                mkdir -p "$(dirname "$database")"
                chown -R redmine:redmine "$(dirname "$database")"
            fi

            cat > './config/database.yml' <<-YML
                $RAILS_ENV:
                  adapter: $adapter
                  database: $database
                  host: $host
                  username: $username
                  password: "$password"
                  encoding: $encoding
                  port: $port
            YML
        fi

        # ensure the right database adapter is active in the Gemfile.lock
        bundle install --without development test
        if [ ! -s config/secrets.yml ]; then
            if [ "$REDMINE_SECRET_KEY_BASE" ]; then
                cat > 'config/secrets.yml' <<-YML
                    $RAILS_ENV:
                      secret_key_base: "$REDMINE_SECRET_KEY_BASE"
                YML
            elif [ ! -f /usr/src/redmine/config/initializers/secret_token.rb ]; then
                rake generate_secret_token
            fi
        fi
        if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then
            gosu redmine rake db:migrate
        fi

        chown -R redmine:redmine files log public/plugin_assets

        if [ "$1" = 'passenger' ]; then
            # Don't fear the reaper.
            set -- tini -- "$@"
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_backlogs ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_backlogs /usr/src/redmine/plugins/
                        bundle update nokogiri
                        bundle install
                        bundle exec rake db:migrate
                        bundle exec rake tmp:cache:clear
                        bundle exec rake tmp:sessions:clear
            set +e
                        bundle exec rake redmine:backlogs:install RAILS_ENV="production"
                        if [ $? -eq 0 ]; then
                echo "installed backlogs"
                                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
                        fi
            set -e
            touch /usr/src/redmine/plugins/redmine_backlogs/installed
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_local_avatars ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_local_avatars /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
                fi
        if [ -e /usr/src/redmine/install_plugins/redmine_slack ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_slack /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_issue_templates ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_issue_templates /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_default_custom_query ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_default_custom_query /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_lightbox2 ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_lightbox2 /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_banner ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_banner /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_dmsf ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_dmsf /usr/src/redmine/plugins/
            bundle install --without development test xapian
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_omniauth_google ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_omniauth_google /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/view_customize ]; then
            mv -f /usr/src/redmine/install_plugins/view_customize /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ ! -f '/usr/src/redmine/plugins/redmine_backlogs/installed' ]; then
            set +e
            bundle exec rake redmine:backlogs:install RAILS_ENV="production"
            if [ $? -eq 0 ]; then
                echo "installed backlogs"
                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
            fi
            set -e
        fi
        set -- gosu redmine "$@"
                ;;
esac

exec "$@"

次はこのイメージをAWSのECSにて動作させます。
(次回に続く)

続きを読む

業務でWebサービス開発をする際に気をつけたいこと(新卒向け)

趣味でも業務でも日々Webサービスを開発しているzaruです。こんにちは。ついにアドベントカレンダーも最終日です。まだサンタとしての仕事が残っています。さて今回は仕事としてWebサービスを開発するときに気をつけたいポイントを紹介します。まぁ仕事に限った話じゃないですが…参考になれば幸いです。特に新卒プログラマあたりに読んでもらえればと思います😀

なお僕の業務上インフラ周りはAWSが多いです。

RASISという指標

RASISという指標があります。コンピュータシステムの評価指標5つの頭文字を取ったものです。

  • Reliability(信頼性)
  • Availability(可用性)
  • Serviceability(保守性)
  • Integrity(保全性)
  • Security(機密性)

今回はこの5つの指標に沿ってポイントを紹介していきます。RASIS自体については色々なところで解説されていると思うので、今回は省きます。

信頼性

システムの故障しにくさを表します。

デプロイ/ロールバックの手順を完璧にしよう

Webアプリはデプロイしないと始まりません。まずはデプロイを自動化しましょう。誰がやっても必ずデプロイできるように手順を明記し環境を作りましょう。今時のWebアプリであればデプロイツールをCI通じて、マージされたらテスト実行して自動デプロイ…というのが一般的だと思います。僕の場合はCapistrano + CircleCI + GitHubで構成することが多いです。

また、よく見落としがちなのがデプロイのロールバックです。新機能をリリースしたは良いけれど思わぬ障害が発生し、とりあえず前のコードに戻そう!ということがあります。嫌ですね😖。でも大丈夫、たいていのデプロイツールにはロールバックの機能がついています。

ただし…ロールバックするのですがぶっつけ本番で、使い方もわからないし動かしては見たけど別の障害が発生してしまった…!ということがありえます。二次災害怖いですね。

大切なのはWebアプリの本番公開前にかならずデプロイとロールバックのテストをして手順を完璧にしておくことです。心理的不安をなるべく取り除いていきましょう。

例外設計をしよう

例外設計をしようというのは至極当然の話なんですが意外と抜けていたりします。コードレビューをする中でも特に気をつけたいところです。正常系だけを想定してコードを書いて手元では上手く動いても本番の環境では上手く動かないことがあります。というか、たいてい上手く動きません。

僕の中では以下の様なことを心がけています。

  • 例外を乱用しない

    • 例外は呼び出す側がINPUTの条件を満たしているが、呼び出された側でOUTPUTの条件を満たせなくなったときに投げる
  • 例外は無視しない
    • 受け取るけど握りつぶさない
    • だましだましでアプリを延命させない

例外については下記の記事が大変参考になります。

死活監視/障害検知をしよう

Webアプリが提供できない=事業の機会損失にダイレクトに繋がるので、インフラの死活監視や障害検知は特に大事です。必ずリリース前に各種設定がなされているか確認をしましょう。

  • エラー検知ツール(sentry|AirBrake)を導入する
  • リソースモニタリングツール(newrelic|mackerel|zabbix)を導入する
    • アラートがslackやメールなど担当者に適切に届くように設定をする
  • プロセス管理ツール(monit)を導入する

個人的には sentry / mackerel がお気に入りです。サーバー台数やサービス数がそこまで多くなければ費用もそこまで高くなく、運用コストが低いのが良いです。

ログファイルを適切に扱おう

時々見かけるのがログファイルのローテーションがされずにディスクフルで死亡するケースです。プロビジョニングツールなどで標準化するなどして人の手でうっかり設定漏れをなくしましょう。

また、アプリケーション内で必要なログを別途出力するようにすると障害時に検証しやすくなると思います。

バックアップを取ろう

はい。当たり前ですね。どの程度のバックアップが求められるかはサービスによって違いますが、少なくともまったくバックアップが取られていないという状況は今すぐやめましょう。

  • DBはAWSならRDSを使って自動バックアップを取る

    • またはバッチで深夜定時バックアップを行う
  • 画像などのリソースはAWS S3のバージョニングを使う

可用性

システムが継続して稼働できる能力。サービスによってSLAと予算が変わってくるため、どこまでやるかはバランスを見ながらになります。

単一障害点をなくす

どこかのサーバや機器が壊れたらシステム全体が提供不能になるような単一障害点は、なるべく取り除いていきましょう。特にコストが殆どかからないような取り組みは積極的にやりましょう。一度ローンチしたシステムのインフラ構成をガッツリ変えるのは精神的に削られるので…。

  • ALB/ELBなどのロードバランサを利用する

    • 別のAZにインスタンスをたてる
    • 要件によってはオートスケールに対応する
  • CloudFront / AkamaiなどのCDNを利用する
    • オリジンにはS3を利用する
  • RDSのMultiAZを利用する
    • Auroraを利用する

参考記事

属人化を防ぐ

インフラなどの単一障害点をなくす話はよく出てきますが、人的単一障害点もなくすようにしましょう。組織としてのスケールと強さが出てくるところだと思います。そのためには属人化を防ぐ必要があります。

  • ドキュメントを整備して誰でもできるようにする
  • レビュイー固定をなくしてレビュー速度を上げる
  • こんな固定タスクがあるよ!と声を上げる

参考記事

保守性

メンテナンス/障害対応のしやすさ。

テストコードを書こう

散々言われてきていることなので割愛します。テストコードを書きましょう。TDDである必要も、カバレッジ100%である必要もありませんが、テストコードを書かなくても良いということはないと思っています。

ドキュメントを残そう

ドキュメントを残すことを残業のように思うのではなくドキュメントも含めて開発という意識にすると良いと思います。コードと同一視するということです。そうすることで、関係者誰でも閲覧・更新ができて、場合によってはバージョン管理することで差分を確認することができるようになり、ドキュメントを育てる土壌の第一歩が踏めます。

ドキュメントを育てる土壌に必要なのは以下のようなポイントだと思っています。

  • ドキュメントの場所を集約する
  • Markdownなどのプレーンテキストに近いフォーマットを採用する
  • 誰でも閲覧・更新できるようにして、チームの共同作業と意識する
  • 必要十分に書き、最初から全てを網羅しない。必要になったときに追記する
  • 必要なくなったドキュメントは消す

障害情報をログに残そう

業務で開発をしていると必ず障害対応というものに出くわします。突発作業になることが多いため、どのような原因で、どうやって対処したのかをドキュメントに残さず終了してしまうことがありますが、それではチーム全体で共有できず再発してしまう可能性や、他プロジェクトを開発する際に教訓を活かせなくなってしまいます。このログが明日の自分を守る武器になります。

読みやすいコードを心がけよう

これも散々言われてきていることですね。まずはリーダブルコードを読みましょう。そして、チームで読みやすいコードとはなにかを議論しましょう。考え方が違うとコードレビューで無駄なやり取りが発生してしまいます。縛りすぎないコーディング規約を設定するのも良いと思います。

開発環境を簡単に作れる

一人でガリガリと書いているような環境だと、開発環境の構築がおざなりになってしまい、他のプログラマがジョインした時に環境構築だけで1日が終わる…しかもドキュメントが残ってないから質問にも答えなきゃいけない…ということになります。

今であればVagrantやDockerがありますし、プロビジョニングツールもChef / Ansible / Puppet / Itamaeといろいろあります。構築ドキュメントも含めて誰にも聞かずにすぐに立ち上げられるように最初から準備をしておきましょう。

負債を意識して使い、返済計画を作る

業務なので必ず期限が設定されています。そこに間に合わせるために生み出してしまった負債はちゃんと意識しましょう。後で取り返しのつかないような負債だけは積まないようにしましょう。取り返しが付くのであれば、いつどうやって返済するかの計画を、コードレビュー時にチームメンバーと共有すると良いと思います。

当たり前だけどバージョン管理をしよう

これは…さすがにね。

保全性

データの矛盾がなく一貫性が保てるかを表します。

モデル設計は最後まで拘ろう

モデルの設計が間違っていると後々までずっと負荷をかけてきます。常に大リーグボール養成ギブスをしているようなものです。リリースする最後の最後まで拘って、あやしいところはコストかけてでも直したほうが長期的に見たらメリットがあると思います。

トランザクション

新人のコードレビューをすると、トランザクションは知っているけど適切に使われていないというケースを時々見かけます。データの一貫性を保つのは非常に大事です。

DBの制約を利用しよう

まずは、SQLアンチパターンを読みましょう。そしてNOT NULL制約と外部キー制約をうまく使いましょう。

機密性

セキュリティ/不正アクセスがされにくいことを表します。

SSLは全ページで使う

とりあえずSSLを使うかどうかは迷うわず全ページ常時SSL一択にしましょう。購入費用自体も安価になりましたし、Let’s EncryptAWS ACMなど無料で取得できる証明書も登場しています。昔言われていた暗号化する際のCPUコストも正直無視できるレベルです。

なぜ常時SSLにするのかというと

  • HTTP/2が利用できる
  • HTTP / HTTPSの切り替えを行う必要がなくなる

というメリットがあるからです。

またSSLの診断ツールもあるので適宜利用しましょう。

パスワードはまじでハッシュ化しよう

個人情報の流出でパスワードがそのまま保存されていました…とか、2016年でも聞くことがありますが、本当に止めましょう。不可逆なハッシュ化をすることで最悪流出をしても悪用されないようにします。

そして単にハッシュ化すればOKなのか?というとそうではありません。以下の4つを意識する必要があります。

  • ハッシュアルゴリズムを検討する
  • saltを付け加える
  • ストレッチングする
  • 文字数/文字種を増やす制約を付ける

例えば単純なMD5でのハッシュを使って「password」をハッシュ化します。

Digest::MD5.hexdigest('password')
=> 5f4dcc3b5aa765d61d8327deb882cf99

これで大丈夫。とか言うエンジニアが身近に居たらなぐってOKです。レインボーテーブルというテクニックがあり、あらかじめハッシュから平文を参照できる逆引き用辞書を作っておくことで短時間で平文が取得できるようになります。

そこでパスワードの前後に文字列を付け加えるsaltと、SHA-2を利用して対抗します。

Digest::SHA256.hexdigest('salt-password-salt')
=> 4a83e8a5dcc4f9c394c34bf5db03bf5d9197e4a10ddf35dfe4d3406c79e21239

また、ストレッチングをすることで解析までの時間稼ぎもすることができます。

def stretching(str, n = 0)
  str = Digest::SHA256.hexdigest(str)
  return str if n > 1000
  stretching(str, n + 1)
end

stretching('salt-password-salt')
=> 605a721f0126c3db44c2129c6dccb380ce84915357c16ef77dd7ba2ad55a74b6

ここまでの努力をしても入力されたパスワードが1文字とかになると無駄になるのでパスワードのバリデーションもちゃんとしましょう。

適切な認証認可

例えば管理画面のユーザが admin ただ一人とか、ユーザごとに分かれているけど全員スーパーユーザとか、「ID: taro / PW: yamada」みたいな安易なパスワード設定をして担当者に渡すのはやめましょう。

余計なライブラリ・アプリケーションは入れない

開発時は必要だったけど結局使わなくなったライブラリとか、検証するのに入れてたけど本番では必要ないアプリケーションとかは消して必要なプロセスだけ上げるようにしましょう。インフラ構築に慣れていない初心者の場合、けっこうこういうケースを見かけます。リソースも食うし余計な不具合を生む可能性が上がってしまいます。

SELinuxの存在

SELinuxちゃんと使おうよ!という話もありますが、個人的にはSELinuxは無効にして運用しています。理由としては単純に費用対効果で、SELinuxを理解して運用できる技術者が必要なのと対応工数のコストが、得られるメリットに見合わないと感じているからです。とはいえ、思考停止で無効にするのはアホなのでちゃんと向き合う必要があるとも思っています。

オレオレフレームワークは使わない

素直にRailsを使って周辺ライブラリ含めて適切にバージョンアップしましょう。

勉強としてのオレオレフレームワーク構築は非常に良いと思います😀

おまけ

パフォーマンス

Webサービスが想定しているユーザ数・トラフィック・データ量を適切に処理できるシステム設計になっているかを検証しておきましょう。極端ですが一人しか使わないようなシステムに複雑な分散アーキテクチャを適用しても豚に真珠🐷💎状態ですし、数億レコードを超えるようなシステムで1台のMySQLサーバだけというのは無理があります。

心構え的な所

昔書いたものがあるので良ければ読んでみてください。

リリース前チェックリスト

Webサービスを開発/リリースする前にチェックするリストです。会社やチームによって細かいところは違うと思いますが、思いつくものをリストアップしてみました。

サーバ

  • エラー検知ツール(sentry|AirBrake)を導入している
  • リソースモニタリングツール(zabbix|newrelic)を導入している
    • アラートがslackやメールなど担当者に適切に届くように設定している
  • ソースコードのバージョン管理がされている
    • GitHubでプルリク開発フローを整備し、ドキュメント化されている
  • サーバの起動/再起動/停止などのオペレーションがドキュメント化されている
  • Itamae、Chefなどでインフラの自動構築が本番・ローカル問わず可能
    • 開発環境構築がドキュメント化されている
  • ステージング環境が存在している
    • ステージング環境にはBASIC認証などがあり、Googleにクロールされないようになっている
  • ダウンタイムなしで、スケールアウト/サーバの入れ替えが可能な構成になっている
  • メール送信が迷惑メールにならないように設定されている
    • AWS SESの場合

      • DKIMを設定している
      • バウンスメール/迷惑メールに対応している
  • 各種アプリケーションのログがローテーションされる
  • 想定トラフィックに耐えられるか事前に本番データ量で検証している
  • ServerTokenを表示しないようにしている
  • テキストコンテンツにgzip圧縮が設定されている
  • アセットファイルにexpiresが設定されている
  • SSHでパスワード認証ができないようになっている
    • rootユーザでログイン出来ないようになっている
  • 本番環境で、アプリケーションのエラーメッセージが表示されないようになっている
  • 設定ファイルなど外部から参照できるようなパーミッション/パスになっていないか
  • WAFを導入しているか

サービス運用

  • デプロイツールが用意されている

    • 自動デプロイが可能になっている
  • 対象ブラウザでの動作テストを行っているか
  • 404 / 500エラーページを用意しているか

データベース

  • 特定のサーバからのみ接続できるようになっている
  • データベースのバックアップが設定されている
    • データベースのロールバック手順がドキュメント化されている
  • 実行されるSQLにそくしたインデックスが作成されているか
  • 想定のレコード数をいれた状態でパフォーマンステスト済みか

SEO

  • Googleタグマネージャーを導入している

    • クリックイベントを設計しているか
  • Google Analyticsが登録されている
  • Search Consoleが登録されている
  • title/descriptionなどMETAタグが適切に設定されている
  • 本番環境のrobots.txtやmetaタグでクロールされないようになっていない
  • sitemap.xmlを用意しているか
  • ogpが適切に設定されているか
  • TwitterCardが適切に設定されている
  • wwwありなしなどドメインが統一されているか

アプリケーション

  • 環境に依存するようなパラメータをハードコーディングしていない
  • ユニットテストが作られている
  • E2Eテストが作られている
  • 自動テストの環境が構築されている
  • favicon.icoは設置されているか
  • サーバ側で生成されるファイルが、サーバに依存していない(S3を使うなど)

パフォーマンス

  • 無駄にファイルサイズの大きい画像を使用していないか

    • レスポンシブデザイン時にsrcsetを適切に使えているか
  • ページ表示速度が適切か
    • 定期的な測定ができるようになっているか
  • Google PageInsightで必要最低限の対応ができているか

さいご

Webサービスの開発に終わりはありません。常に始まりであり終わりであり改良をし続けていく必要があると思っています。自分が作っているWebサービスが解決しようとしている問題を、技術的な部分で邪魔しないように、意識できる所・工夫できる所はどんどん増やしていっていきたいですね😀

続きを読む

熊本地震のときに作成したWebサービスの概要

この投稿は 防災・減災 Advent Calendar 2016 の 15日目の記事です。

はじめに

taskey株式会社でフロントエンジニアをやっております。
深見と申します。

私たちは熊本地震が起きた際、【熊本地震 情報掲示板】というWebサービスを開発しました。
そのWebサービスの内容や今回の開発で得た知見などを残しておきたくアドベントカレンダーに投稿させていただいてます。
稚拙な文章で伝わりづらい部分もあるかと思いますが、最後まで読んでいただけると幸いです。

また震災のこと + システム周りのことだったのでqiitaに投稿するか迷ったのですが、どたらかというとエンジニアの方向けの知見だったのでqiitaに投稿させていただきました。

熊本地震について

熊本地震(くまもとじしん)は、2016年(平成28年)4月14日21時26分(日本標準時[注釈 1])以降に熊本県と大分県で相次いで発生している地震である。
気象庁震度階級では最も大きい震度7を観測する地震が4月14日夜(前記時刻)および4月16日未明に発生したほか、最大震度が6強の地震が2回、6弱の地震が3回発生している[JMA 2]。日本国内の震度7の観測事例としては、4例目(九州地方では初)[4]および5例目[JMA 3]に当たり[注釈 2]、一連の地震活動において、現在の気象庁震度階級が制定されてから初めて震度7が2回観測された[5]。また、一連の地震回数(M3.5以上)は内陸型地震では1995年以降で最多となっている[6]。

wikipedia 熊本地震 (2016年)より引用

2016年4月14日に熊本で地震が起きました。
僕達taskeyチームは東京を開発拠点にしているので実際に被災したわけではありませんが、熊本に縁のあるメンバーも多く(僕は大学時代を熊本で、COOは生まれも育ちも熊本)、何かしたいと心のなかで思っていました。

そんな時、熊本地震の情報を得ようといろんなものを見ているうちにある問題点を見つけました。

災害時における情報の問題点

情報の判別の難しさ

何かの情報を見つけたい場合、身近なスマートフォンなどから情報を取得することが出来ます。
震災に関する情報ももちろんそうです。

震災時の場合、情報を得ることが出来る媒体として、facebook, twitter, instagramなどのSNS、熊本県や市役所などの行政機関が出している情報などが存在します。
ただ、2つの情報にもメリットデメリットがあります。

発信源 メリット デメリット
SNS ユーザー投稿形なので情報の更新が非常に早い 正しい情報もあれば間違った情報も含まれる
行政 情報の更新が早いわけではない 基本的に情報が正しい

一般的にもよく言われていることですが、ユーザーが投稿するSNSの情報は更新が非常に早く、信頼度は高くはない。
震災などの緊急時は通常時より情報が非常に多く流れていくためどれが正しい情報であるかを判断することは非常に難しいです。本当に正しい情報が含まれている場合でも他の情報に埋もれてしまい発見できないこともあるでしょう。

こういう場合に本当に信頼できる情報は市役所などの行政機関が公式に発表している情報です。

正しい情報を得る手段がない

上の表でも書いたとおり、行政機関の情報のほうが信頼度としては高いですし、震災時には各機関のWebサイトに震災に関する情報などが掲載されていました。

  • 熊本市役所 ・・・ 震災情報、避難情報, etc…
  • 熊本水道局 ・・・ 水道情報、断水情報, etc…
  • 気象庁 ・・・ 日本全体の地震速報
  • その他行政 ・・・ 各自治体で同じように情報掲載

しかし、震災という緊急時に多くの人がアクセスし、市役所や水道局などのWebサイトへを開くのが非常に困難な状態になってしまっていました。
レスポンス自体が返ってこずタイムアウトしてしまうこともありました。

そこで上記問題点を解決すべく以下の3点に集中してサービスの開発を行いました。

  1. 多くのアクセスにも耐えることが出来るサーバを使用する
  2. 熊本市や水道局などの行政からでている正しい情報を記載している
  3. ユーザー投稿型で情報を記載することが出来る

次に開発したサービスに関して説明します。

熊本地震 情報掲示板に関して

僕たちは【熊本地震 情報掲示板】(既にサービス自体は閉じています)というものを作成しました。
縦に長くなってしまって申し訳ありませんがページ全体のスクリーンショットを全て貼っておきます。

サイト内にある情報は主に以下のとおりです。
また、詳細は画像を見ていただけると幸いです。

  • 熊本市、水道局、気象庁などの行政機関から発信されている情報
  • 避難所や炊き出し・配給所、給水場所、SOS情報、ボランティア応募などの一般の方が投稿できる情報
  • 義援金などの外部の方からの支援の情報

screencapture-localhost-3000-1481724999292.png

アーキテクチャ全体

シンプルですが全体の構成図をまとめました。

kumamoto-jishin_architecher.jpg

システム全体の仕組みは以下の2つしかありません。

  • サイトにアクセスしたユーザーはRoute53を通じS3バケットへアクセス。S3バケット内に存在するindex.htmlファイルを返す。
  • 常に最新で、正しい情報を更新するためにEC2から熊本市役所、水道局、気象庁などの行政機関に定期的にアクセス。収集した情報から静的なhtmlファイルを生成しS3へアップロード

仕組みの解説も含め内容を紹介していこうと思います。

システム詳細

1. 多くのアクセスにも耐えることが出来るサーバを使用する

まず多くのアクセスが来ても問題なくレスポンスを返すことができるようなサーバを探しました。

確かにwebサーバの台数を増やし、ロードバランサー等の設定を行えば問題ないのでしょうが、そのようなものを用意する時間もありませんし、緊急時にどのくらいのアクセスが来るのかもわかりません。
サーバへの負荷のせいで情報へアクセスできないことは絶対に起きてはならないので同時リクエスト数が膨大な数になっても問題ないようなサービスを探していました。

幸いにもAWS(Amazon Web Service)のS3(Simple Storage Service)を業務で使用しており、1秒間に多くのリクエストが来ても問題ないと認識していたのでそちらを使うことにしました。

また、S3はストレージサービスなので静的ファイルしか置くことが出来ません。
なので今回はhtmlをEC2を利用してhtmlファイルを作成し、S3へアップロードする方法を取りました。

画像で言うと以下の部分になります。
kumamoto-jishin_architecher_focus_s3.jpg

2. 熊本市や水道局などの行政からでている正しい情報を記載している

S3上にhtmlファイルを置いておき、アクセスユーザーにそれを返すことで安定してアクセスをさばくことができました。
次に常に正しい情報をhtmlとしてS3においておく必要があります。
正しい情報とは繰り返しになりますが行政機関がWebサイトに載せている情報です。

  • 熊本市役所 ・・・ 震災情報、避難情報, etc…
  • 熊本水道局 ・・・ 水道情報、断水情報, etc…
  • 気象庁 ・・・ 日本全体の地震速報
  • その他行政 ・・・ 各自治体で同じように情報掲載

行政機関が掲載している情報をhtmlにまとめるのにはWebスクレイピングの技術を使用しました。
Webスクレイピングとは必要な情報を指定したWebサイトから取得するような技術のことです。
今回はWebスクレイピングを用い、上記サイトから情報を収集し、その情報からhtmlファイルを作成しS3へアップロードするようにしました。
また、スクレイピングのアクセス自体が行政のサイトへのアクセス負荷につながってはいけないのでスクレピング自体は1分間隔で行いました。

画像で言うと以下の部分になります。
kumamoto-jishin_architecher_focus_ec2.jpg

3. ユーザー投稿型で情報を記載することが出来る

これにはgoogle formとgoogle spreadsheetを使用しました。
上記サービスを利用したのは、時前で作るよりも巨人の肩に乗ったほうが安定している&開発速度も上がるからです。
ここには、避難所情報や炊き出し、トイレ、コンビニ、SOSなどの多くの機能を投稿できるようにしました。

問題点

上記のようなシステムでWebサイトを作成したことで多くの人からアクセスがあっても目に見えてレスポンスの速度などが遅くなったことはありませんでした。
Google Analyticsの情報によると2300人くらいが同時アクセスしてもまったく問題ありませんでした。

また、このシステム自体の企画・開発・リリースまで約12時間ほどで行うことが出来ました。
数日ほど時間を書けて開発を行えばもっと立派?なものが出来たかもしれませんが、震災などのが起きているときにそのような悠長なことは言ってられないと思っていたので約半日で完成できたのは良かったと思っています。

しかし、完ぺきにできたわけではなく問題点も存在しました。

スクレイピング先Webサイト構造の変更

今回のWebサイトでは行政の情報を取得してくるWebスクレイピングの技術が非常に重要になりました。
Webスクレイピングとは実際にシステム側から行政webサイトにアクセスしその中のDOM(htmlの構造)から対象となる部分を見つけ出しそこから情報を抽出するといったことを行なっています。

そのため、行政側のWebサイトの構造が変更されてしまっては正しい情報が取得することが出来ません。
このようなことが実際数回起きてしまい、その間は正しい情報が取得できていないことがありました。

スクレイピング先Webサイトにアクセスできない

これは想定していたことですが、そもそもスクレイピング先のWebサイトにアクセス出来ないということもありました。
webブラウザを通じてアクセスするときと同じようにリクエストがタイムアウトしてしまうといった感じです。
幸いにも時間をかけるとレスポンスは返ってくることが多かったので、返ってこない場合は前回取得した情報を残したまま最終更新日時を前回のものにするという対応を行いました。

そもそもサイトが信用できない

僕達のサイトは正しい情報源の情報を取得して掲載していましたが、その情報が正しい情報かどうかはアクセスしていただいている人にはわかりませんし、運営元の僕達が信用できるかも非常に判断しづらいところでした。

個人的に思う解決策

アクセス増加をさばくようなサーバの設計にする

そもそも信頼できる情報を載せている行政機関のサイトのサーバを増強 or 高アクセス時にスケーリング出来るように設計するという方法です。
行政機関のWebの仕組みや予算などをまったく知らないのであまり詳しくお話することは出来ませんが、現実的ではなさそう。。。

APIを用意する

各行政機関には緊急時に情報を公開するAPIを作成していただければ非常に助かります。
通常のアクセスをさばくサーバとは別に用意していただき、そこへ開発者はアクセスし緊急の情報をjson形式で返してもらえるとそれを使って情報を公開することが出来ます。
また、そのようなAPI一覧が集まっているページなどを作ってそこにAPIの仕様をまとめるなども必要だなと思っています。

謝辞

今回は非常に多くの方に助けていただいて本サービスをリリースすることが出来ました。
僕たちtaskey株式会社はスタートアップ企業であるので僕達一人の工数を取ることは当たり前ですが会社としてはリスクになります。
そういう中でも「社会的に意義があることならやるべきだ」と開発を中断してもやるべきだと背中を推してくれた弊社CEOの沼澤には非常に感謝しています。
また、業務と並行しつつ外部とのやり取りや開発を続けた、弊社COO大石、エンジニア石山にも感謝しています。

また、Increments株式会社プロダクトマネージャーであり、IT DARTの代表理事である及川卓也さんにも様々なアドバイスを頂いたり、関連する救助団体の方や有識者の方とお話する機会をいただきました。ありがとうございます。

また、熊本地震情報掲示板に掲載されている情報を元に実際に被災地で救助活動などを行なってくれた熊本の方々にも本当に感謝しています。

SNSで拡散を手伝ってくださった多くの方にも非常に感謝しています。
わたしたちのサイトは正しい情報を載せているといっても実際にその根拠はありません。
そんな中twitterやfacebookなどで多くの方、SNS上で影響力のある芸能人の方や著名人の方にも多く拡散していただきました。
ありがとうございます。

個人的感想

実際にどのくらい影響があったか何人の人を助けることが出来たかは定かではないですが、SNSや実際の知人の感想をいただいてる様子だと少しは被災した方の役に立ったのではないかなぁと思っています。

好きでプログラミングをはじめて多くのサービスを出してきましたが熊本地震情報掲示板を開発して感謝の言葉を目にしたときに「プログラミングやってきて本当によかったな」と強く感じました。
多分単なる自己満足でしかないと思いますが、Webと言うかたちが見えないもので、人の力になれたことが本当に嬉しかったです。

長くなってしまいましたがこれで終わりです。
何か皆様の知見になっていたらうれしいです。
皆様ここまで読んでいただきありがとうございました。

続きを読む