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にて動作させます。
(次回に続く)

続きを読む

AWS Elastic Beanstalkでmonit監視するための注意点

最近、インフラのお仕事をしています

Ruby on railsのアプリケーションでよしなにキューを使いたいという用件があり、shoryukenを使っておりました
sidekiqでいいんじゃね、というツッコミはなしにして、インフラ担当の自分はデプロイ時下記の要件を満たすインフラを作る必要がありました

  • shoryukenのジョブが立ち上がっていなかったら立ち上げる
  • shoryukenのジョブが落ちたら復旧する

元々Elastic Beanstalkで構成していたのですが、上記の用件を満たすために .ebextensions で実現しようとしましたがなかなかハマったのでメモを残しておきます

shoryukenを立ち上げるために

shoryukenを立ち上げるために下記のファイルを配置しました

 .ebextensions/shoryuken.config
files:
  "/opt/elasticbeanstalk/hooks/appdeploy/post/50_restart_shoryuken":
    mode: "000777"
    content: |
      . /opt/elasticbeanstalk/support/envvars
      . /opt/elasticbeanstalk/support/scripts/use-app-ruby.sh

      # undefined $HOME when execute this script, so abort `bundle exec shoryuken`
      HOME=/root; export HOME

      mkdir -p /var/app/support/pids
      mkdir -p /var/app/support/logs

      cd /var/app/current

      if [ -f /var/app/support/pids/shoryuken.pid ] && \
         ps aux | grep `cat /var/app/support/pids/shoryuken.pid` | grep 'shoryuken'
      then
        kill -TERM `cat /var/app/support/pids/shoryuken.pid`
        rm -rf /var/app/support/pids/shoryuken.pid
      fi

      sleep 10

      bundle exec shoryuken \
        -R \
        -P /var/app/support/pids/shoryuken.pid \
        -L /var/app/support/logs/shoryuken.log \
        -d

  "/opt/elasticbeanstalk/hooks/appdeploy/pre/03_mute_shoryuken":
    mode: "000777"
    content: |
      if [ -f /var/app/support/pids/shoryuken.pid ]
      then
        kill -USR1 `cat /var/app/support/pids/shoryuken.pid`
      fi

HOMEが定義されているのが渋いところなのですが、shoryukenを実行するコードの中にデフォルトで ~ にアクセスしているコード(ライブラリ側)があり、monitから叩かれる際には root なので、HOMEがない状態で、タスク自体がabortしてしまいます。ちょっと他に回避策が思いつかなかったので、緊急回避的にこうなっています。

monit管理下におく

 .ebextensions/monit.config
packages:
  yum:
    monit: []
commands:
  00_copy_monit_config:
    command: "cp /var/app/support/monit.d/shoryuken.conf /etc/monit.d/shoryuken.conf"
  01_monit_chkconfig:
    command: "/sbin/chkconfig monit on"
  02_monit_restart:
    command: "/sbin/service monit restart"
    ignoreErrors: true
files:
  "/var/app/support/monit.d/shoryuken.conf" :
    mode: "000644"
    owner: root
    group: root
    content: |
      check process shoryuken-proc with pidfile /var/app/support/pids/shoryuken.pid
      start program = "/usr/bin/sudo /opt/elasticbeanstalk/hooks/appdeploy/post/50_restart_shoryuken"
      stop  program = "/usr/bin/sudo /bin/kill -QUIT kill -TERM `cat /var/app/support/pids/shoryuken.pid` && rm -rf /var/app/support/pids/shoryuken.pid"

注目すべきは一番最初のcommandのcopyなのですが、これは直接 /etc/monit.d/shoryuken.conf を配置するとEBが気を利かせて過去ファイルを /etc/monit.d/shoryuken.conf.bak を配置してしまい monit が立ち上がらないという状態になってしまいます
これはデフォルトの monit.confが /etc/monit.d/* のファイルすべてをIncludeしており、過去ファイルまでIncludeするとプロセス名が被ってしまうためです

回避策として /etc/monit.d/ 外にファイルを一旦配置して、それをコピーすることにより
*.bak ファイルの影響を受けないようにしました

まとめ

下記の二点の気持ち悪い機転を聞かせた対応により、shoryukenを起動しプロセス監視できるようになりました

  • ダミーの環境変数定義
  • confファイルの一次退避

EBを使っていると遭遇する問題かと思いますので、何かの参考になりましたら幸いです

続きを読む

〇FLAGの中の人に憧れてMastodon×AWSでモンストドン作ってみた

Mastodon立ち上げたらいい会社に入れると聞いて、邪な気持ちで。。。いや、Mastodonとモンストって相性よさそうだなぁと思いたち、少し乗り遅れた感をかもしだしながら、フルにAWSを使って規模拡大しても大丈夫な構成で作ってみた。

モンストドン (https://monstdn.com)

構成

monstdn.png

最小構成のざっくり料金($1=113円、1ヶ月30日計算)

サービス 単価 月額料金
ALB 1台 × $0.0243/1H + データ転送的なの  約2000円 + α
EC2 2台(t2.nano) × $0.008/1H + データ転送的なの 約1300円 + α
RDS 1台(db.t2.micro シングルAZ) $0.028/1H + データ転送的なの 約2300円 + α
ElasticCache 1台(cache.t2.micro) $0.026/1H + データ転送的なの 約2100円 + α
S3Bucket $0.025/GB + リクエスト数的なの + α
SES $0.10/1,000通あたり + データ転送的なの + α
合計     ( 約7700円 + α なので ) ざっくり1万ぐらい

※無料枠があるので1年目はもう少しやすくできそう

やったこと

  • AWSのアカウント作成
  • IAMの作成とアカウントの初期設定(二段階認証とか、パスワードポリシーとか)
  • Route53でドメインを買う
  • SESでメール設定と制限解除申請
  • ACMの取得(無料でHTTPS通信)
  • S3バケット作成(画像とかのアップロードファイルの配信用)
  • VPCとセキュリティグループの作成
  • SES、S3へアクセスする為のIAMユーザの作成
  • ElasticCacheでRedisの作成
  • RDSでPostgreSQLの作成
  • EC2でCentOSを使ってMastodonの構築(下に詳細)とイメージ(AMI)の作成
  • AutoScallingの設定
  • ALB(ApplicationLoadBalancer)の作成(ACMをつける)
  • Route53でHostZoneのレコード設定

CentOSでのMastdon構築(20170517現在)

sudo su -
yum -y update
yum -y install vim

localectl set-locale LANG=ja_JP.utf8
localectl set-keymap jp106
localectl status

timedatectl set-timezone Asia/Tokyo
timedatectl status

dd if=/dev/zero of=/mnt/swapfile bs=1M count=2560
mkswap /mnt/swapfile
swapon /mnt/swapfile
chmod 0644 /mnt/swapfile
echo "/mnt/swapfile                             swap                    swap    defaults                0 0" >> /etc/fstab
free

vim /etc/sysconfig/selinux
 SELINUX=enforcing
 ↓
 SELINUX=disabled

systemctl disable postfix
systemctl disable auditd.service

yum -y install libxml2-devel ImageMagick libxslt-devel git curl nodejs file
yum -y install epel-release
rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum -y install ffmpeg ffmpeg-devel

yum -y group install "Development tools"
curl -sL https://rpm.nodesource.com/setup_4.x | sudo bash -

yum -y install nodejs
npm -g install yarn

yum -y install postgresql postgresql-contrib postgresql-devel
yum install -y openssl-devel readline-devel

useradd mastodon
passwd mastodon
su - mastodon
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src && cd ~
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && source ~/.bash_profile
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.4.1 && rbenv global $_ && rbenv rehash

# 確認
ruby -v

cd ~
git clone https://github.com/tootsuite/mastodon.git live
cd live
git checkout $(git tag | tail -n 1)

gem install bundler
bundle install --deployment --without development test
yarn install --pure-lockfile

cp .env.production.sample .env.production
sed -i "/^PAPERCLIP_SECRET=$/ s/$/`rake secret`/" .env.production
sed -i "/^SECRET_KEY_BASE=$/ s/$/`rake secret`/" .env.production
sed -i "/^OTP_SECRET=$/ s/$/`rake secret`/" .env.production

vim .env.production
#Redis,Postgresql,言語,SMTP,S3の設定

RAILS_ENV=production bundle exec rails db:setup
RAILS_ENV=production bundle exec rails assets:precompile

exit

cat << "_EOF_" > /etc/systemd/system/mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_


cat << "_EOF_" > /etc/systemd/system/mastodon-sidekiq.service
[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="DB_POOL=5"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

cat << "_EOF_" > /etc/systemd/system/mastodon-streaming.service
[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="NODE_ENV=production"
Environment="PORT=4000"
ExecStart=/usr/bin/npm run start
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

systemctl enable mastodon-{web,sidekiq,streaming}
systemctl start mastodon-{web,sidekiq,streaming}

cat << "_EOF_" | crontab -
RAILS_ENV=production
@daily cd /home/mastodon/live && /home/mastodon/.rbenv/shims/bundle exec rake mastodon:daily > /dev/null
_EOF_

yum -y install nginx

cat << "_EOF_" > /etc/nginx/conf.d/mastodon.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name {domainName};

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://localhost:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}
_EOF_

systemctl enable nginx
systemctl start nginx

# ユーザ登録後 admin設定
RAILS_ENV=production bundle exec rails mastodon:make_admin USERNAME={UserName}

メモ

EC2のDiskはSSDで(swapで使う)
ロードバランサーはApplicationの方じゃないとwebSocketがうまくいかない
コミュニティベースのシステムだからCloudFrontはあまり必要性感じなかったので使わなかった
(日本向けだしS3のバケット東京リージョンにあるし、S3もかなり性能いいし)
もしCloudFrontを使うなら、websocketできないからS3の前に置く感じ
今回CloudFrontの利点があるとすれば”ドメイン”が自分の使えることぐらいかな
CentOSじゃなくてAmazonLinux使いたかったけど、ffmpeg入れるのにやたら時間かかったからやめた。一応動いたけど(純正AWSが。。。)
DockerはDeployまで楽そうだけど、効率よくなさそうだったのでやめた
AWSでDocker使うならECSでやってみたいけど、Mastodonはすんなりできるのかなー
セキュリティ的にはロードバランサーからの80番ポートしか受け付けないように制御してるから大丈夫かな。
sshでのログインは同じVPC内に踏み台サーバ立ててと。

最後に

ここまで読んで頂きありがとうございます。
技術的なことを投稿するのはこれが初めてですが、だれかのお役にたてれたら嬉しいです。
普段はPHPとAWS少しいじる程度なのでいい勉強になりました。
ほとんど公開されている文献をもとにプラモデル感覚で作りましたので、ご指摘等あればコメント頂ければと思います。
個人でのサイト運用となりますので、落ちたらごめんなさい。

続きを読む

JAWS-UG コンテナ支部 #8

DockerCon 2017 報告 @toricls さん

LINUX KIT

  • Dockerエンジンをどの環境でも動作する為のLinuxサブシステムを集めたツール
  • DOCKER.YMLで定義する(ymlで定義)
  • 全てのサブシステムはコンテナ
  • ポータブルなlinuxサブシステム
  • 動作にはMobyが必要

MOBY PROJECT

  • ビルドツール(makeみたいなもの)。
  • アプリケーションエンジニア、インフラエンジニアには必要ない。
  • 将来的にはDockerバイナリをビルドできるようになる。
  • 従来のDockerはDocker社のものになった。

MULTI STAGE BUILDS

  • build用のコンテナを用意する必要なくなった。

    • Dockerfileでビルドを2つ書く

『Dockerで構成するWebサービス~EmotionTechの場合・増補版~』株式会社Emotion Tech 子安 輝さん

  • EmotionTechのDocker導入から運用までの話
  • ElasticBeanstallkを使用
  • phusionのDockerImageを使用(イメージサイズは大きい)。
  • フロント(angular.js)とバックエンド(Rails)とワーカー(SQS)の3つのコンテナ。テスト環境と本番環境は同じ構成。ローカル環境もだいたい同じ構成。
  • 構築する時に気をつけた事
    • ポリシーを持って構築。
    • 各環境(ローカル、CI)で使えるDockerバージョン
    • Dockerfileのお手本参照するべし(https://github.com/docker-library)
    • ローカル環境を本番環境に近づける
  • 使ってみたら起きたこと
    • rails cで30秒待った
    • CIで使いたいコマンドが使えなかった
    • db:migrateする仕組みがない
    • 環境変数でコンテナの挙動を変えたかった
  • 本稼働してから
    • ビルドがだんだん遅くなる
    • 監視は普通にやれた
    • 内部監視はホストのmackerelエージェント。
    • cronで監視ログをマウントしたVolumeに出力して、ホストのmackerelエージェントが監視している。これでうまくいっている。
    • インスタンスをあえて入れ替えた
  • Tips
    • ローカル環境はdocker-composeで。
    • ローカル環境の初期化/更新のスクリプトを用意。
    • ローカル環境の実行環境はDocker for xxxが主流。
    • GithubTag、Dockerイメージタグ、デプロイバージョン、全て同じ値を使用する。
    • 環境変数は設定出来る箇所は複数可能なのでどこで設定するかを整理しておく。
  • ステージング環境で検証したイメージをそのまま本番環境へデプロイする=ステージング環境から本番まで同じイメージを使用する事。
  • Railsを動作しているRails.envはProductionで動作してて、DBの接続先の変更は環境変数で切替している。

LT kenjiszk さん

  • FiNCでのコンテナの管理方法
  • マイクロサービス化しててAmazonECSで解決
  • Dockerビルド&テストはJenkins
  • ECSのクラスターはDeployTask、Web、Batchの3つで運用
  • jenkinsfileをDirectoryTopに用意しておく

LT hokkai7go さん

LT kuntaroIshiyama さん

LT gavinzhm さん

  • DockerHubの脆弱性について
  • DockerHubのイメージで80%以上は少なくても重大な脆弱性がある
    • CommunityImageが更新されていない(1800+)
    • オフィシャルイメージでも(392)
    • ScanningToolを使用するべき(Clairオススメ)
  • yum update apt-get updateする
  • AplineLinux使う
  • ScanTool使う
  • GolangならFROM scrachがある

LT wata727 さん

続きを読む

Opsworks で EC2 インスタンスに保持されるリリースの数を制限する

はじめに

とある Rails アプリで EC2 インスタンスの HD の容量が足りなくなり、それによってエラーが出ることがあったので、いろいろと原因はあるものの、リリースって5つも保持してなくてもよくね?となったので EC2 インスタンスに保持するリリースの数を制限しました。

実装

Stack の設定の中の Custom JSON に下記コードを加えるとリリースの保持数が3になる。

{
  "deploy": {
    "app_name": {
      "keep_releases": 3
    }
  }
}

まとめ

本当はしっかりとどこで容量食ってるのかとか見た方がいいとは思いますが、
リリース数の制限したことなかったのでとりいそぎこちらやってみました。

続きを読む

Rails4+Capistrano3+Nginx+Unicorn EC2へのデプロイ < 後編 >

インフラ勉強中の者がAWSにrailsアプリをEC2にデプロイしたので備忘録として残します。
< 後編 >です。

【イメージ】
スクリーンショット 2017-05-09 11.04.30.png

< 前編 >Unicorn の設定までを実施しました。

デプロイチェック

ローカル
$ bundle exec cap production deploy:check

デプロイチェックをすると下記のようなエラーが出るはずです。

~ 省略 ~
00:02 deploy:check:linked_files
ERROR linked file /var/www/deployApp/shared/config/database.yml does not exist on YOUR_IP_ADDRESS

/var/www/deployApp/shared/config/database.yml が存在しないためのエラーです。

shared ディレクトリは、Capistranoが自動生成するディレクトリです。
EC2の qiitaApp の配下を確認します。

EC2
[myname@ip-10-0-1-86 qiitaApp]$ ls -la
drwxrwxr-x 4 ec2-user ec2-user 4096  5  9 04:08 .
drwxrwxrwx 3 root     root     4096  5  9 04:06 ..
-rw-rw-r-- 1 ec2-user ec2-user    6  5  9 03:33 .ruby-version
drwxrwxr-x 2 ec2-user ec2-user 4096  5  9 04:08 releases ← 自動生成
drwxrwxr-x 7 ec2-user ec2-user 4096  5  9 04:08 shared ← 自動生成

config 配下に database.yml を生成すればエラーは出なくなるのですが、次に同じようにsecrets.ymlが存在しないというエラーが出ますので、共に生成して変更します。

EC2上にdatabase.ymlの生成の前にローカルの database.yml を変更します。

ローカル
$ vi config/database.yml 

======================= database.yml ================================

~ 省略 ~
#production:
#  <<: *default
#  database: db/production.sqlite3
   ↓↓↓
production:
  <<: *default
  adapter: mysql2
  database: qiita_db
  host: qiita_db.xxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com                                                                                                                                                     
  username: qiita_user
  password: YOUR_DB_PASS
  encoding: utf8

====================================================================

変更したら database.yml 全体をコピーして保存します。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd shared/config/
[ec2-user@ip-10-0-1-86 config]$ touch database.yml
[ec2-user@ip-10-0-1-86 config]$ vi database.yml

======================= database.yml ===============================

 ローカルでコピーしたdatabase.ymlを貼り付けます。

====================================================================

同様に secrets.yml もローカルの中身をコピーして(変更はなし)、configの配下に secrets.yml を生成して貼り付けます。

EC2
[ec2-user@ip-10-0-1-86 config]$ touch secrets.yml
[ec2-user@ip-10-0-1-86 config]$ vi secrets.yml

再度、デプロイチェックを実施します。

ローカル
$ bundle exec cap production deploy:check

エラーが出なければdeploy:checkは完了です。

デプロイ

ローカル
$ bundle exec cap production deploy --trace #traceをつけることでバグの箇所を発見しやすくします

エラーが出なければデプロイ完了です。

今回は、scaffoldで簡単なUserアプリを生成しているので、 直接、http://IP_ADDRESS/users/ を叩いて確認してみます。

…おそらく500エラーが返ってきてしまって確認できないと思います。

Unicornの状態とエラーログを確認します。

EC2
[ec2-user@ip-10-0-1-86 config]$ ps ax | grep unicorn
30256 ?        Sl     0:01 unicorn master -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                             
30311 ?        Sl     0:00 unicorn worker[0] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30313 ?        Sl     0:00 unicorn worker[1] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30316 ?        Sl     0:00 unicorn worker[2] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30334 pts/1    S+     0:00 grep --color=auto unicorn

Unicornは稼働していることが確認できます。
次はエラーログです。

EC2
[ec2-user@ip-10-0-1-86 config]$ cd ../..
[ec2-user@ip-10-0-1-86 qiitaApp]$ tail -f current/log/unicorn_error.log 

この状態で再度ローカルからデプロイします。

ローカル
[ec2-user@ip-10-0-1-86 qiitaApp]$ bundle exec cap production deploy
EC2
I, [2017-05-09T06:57:08.516736 #30256]  INFO -- : executing ["/var/www/qiitaApp/shared/bundle/ruby/2.3.0/bin/unicorn", "-c", "/var/www/qiitaApp/current/config/unicorn.rb", "-E", "deployment", "-D", {8=>#<Kgio::UNIXServer:fd 8>}] (in /var/www/qiitaApp/releases/20170509065649)
I, [2017-05-09T06:57:08.735844 #30256]  INFO -- : inherited addr=/tmp/unicorn.sock fd=8
I, [2017-05-09T06:57:08.736108 #30256]  INFO -- : Refreshing Gem list
I, [2017-05-09T06:57:09.978667 #30311]  INFO -- : worker=0 ready
I, [2017-05-09T06:57:09.981219 #30256]  INFO -- : master process ready
I, [2017-05-09T06:57:09.982336 #30313]  INFO -- : worker=1 ready
I, [2017-05-09T06:57:09.985331 #30316]  INFO -- : worker=2 ready
I, [2017-05-09T06:57:10.155786 #29111]  INFO -- : reaped #<Process::Status: pid 29166 exit 0> worker=0
I, [2017-05-09T06:57:10.155874 #29111]  INFO -- : reaped #<Process::Status: pid 29168 exit 0> worker=1
I, [2017-05-09T06:57:10.155945 #29111]  INFO -- : reaped #<Process::Status: pid 29171 exit 0> worker=2
I, [2017-05-09T06:57:10.155973 #29111]  INFO -- : master complete

この時点ではエラーらしきものが出ていません。

再度 http://IP_ADDRESS/users/ を叩いて確認してみます。

EC2
E, [2017-05-09T06:59:27.508190 #30316] ERROR -- : app error: Missing `secret_token` and `secret_key_base` for 'production' environment, set these values in `config/secrets.yml` (RuntimeError)

Missing secret_token and secret_key_base とあります。
EC2上の qiitaApp/current/config/secrets.yml の secret_key_base:の環境変数(SECRET_KEY_BASE)が設定されていないことが原因です。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd current
[ec2-user@ip-10-0-1-86 current]$ bundle exec rake secret
1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz ←コピーする
[ec2-user@ip-10-0-1-86 current]$ vi config/secrets.yml 

============================== secrets.yml ==================================

~ 省略 ~
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
          ↓↓↓
  secret_key_base: 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz

=============================================================================

本来であれば、直接入力するのではなく、

SECRET_KEY_BASE=1234567890abcd...
export SECRET_KEY_BASE

とかで環境変数のまま使用すべきです。
が、何回実施してもググってもうまくいかなかったので、取り急ぎ直接入力で対応します。
dotenvというgemがあるようなので、それで対応してもいいと思います。

再度、ローカルからデプロイして、urlを叩けば下記の画面が表示されると思います。
スクリーンショット 2017-05-09 16.28.33.png

大変参考にさせていただきました。
capistranoを使ってrailsをnginx+unicorn+mysqlの環境にデプロイする
ローカルで開発したRailsアプリをCapistrano3でEC2にデプロイする
Capistrano3でUnicorn+Nginxな環境にRailsをデプロイする:初心者向け
Railsプロダクション環境 Unicornでsecret_key_baseが設定されていないとエラーが出る

続きを読む

Rails4+Capistrano3+Nginx+Unicorn EC2へのデプロイ < 前編 >

インフラ勉強中の者がAWSにrailsアプリをEC2にデプロイしたので備忘録として残します。
長いので<前編><後編>の2回に分けて実施致します。

【前提】
・今回はscaffoldで作成した簡単なアプリ(userの名前と年齢を登録するアプリ)をデプロイすることをゴールに進めていきます。
・デプロイのディレクトリは /var/www/qiitaAppです。
shared/config 配下のdatabase.ymlsecrets.yml は手動で作成します。
・ローカルへのRubyやRailsのインストールとEC2、RDSのセットアップ(VPCやセキュリティグループ等々の設定)、GitHubのアカウント登録に関しての説明は致しません。
・hostの設定、各環境毎の設定は実施しません。

Rubyのインストール
Ruby on Railsのインストールと設定
AWSの設定

【イメージ】
スクリーンショット 2017-05-09 11.04.30.png

【バージョン】

software version
Ruby 2.3.3
Rails 4.2.7
Nginx 1.10.2
Unicorn 5.3.0
MySql 5.6
Capistrano 3.8.1
capistrano3-unicorn 0.2.1

今回は、デプロイ時にUnicornの再起動もさせるべくcapistrano3-unicornというgemを使用します。
(自身でtaskとして設定する事も可能です。)

GitHubの設定

1)New Repositoryの生成
Repository name → qiitaApp
Public
Create repository

2)ローカルからSSH接続ができるように[SSH keys]を追加する
今回は説明は省略します。
(EC2からもGitHub接続が必要なのでそちらは下記に記載します。)
GitHub 初心者による GitHub 入門(1)

EC2(AmazonLinux)の設定

1) git,rbenvのインストール

[myname@ip-10-0-1-86 ~]$ sudo yum -y install git
[myname@ip-10-0-1-86 ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv #rbenvインストール
[myname@ip-10-0-1-86 ~]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build #ruby-buildインストール
[myname@ip-10-0-1-86 ~]$ sudo vi .bash_profile #.bash_profileの編集

=ファイルの編集画面=

export PATH
export PATH="$HOME/.rbenv/bin:$PATH" ← 追加
eval "$(rbenv init -)" ← 追加

=================

[myname@ip-10-0-1-86 ~]$ source ~/.bash_profile #環境変数の反映
[myname@ip-10-0-1-86 ~]$ rbenv -v #バージョン確認
rbenv 1.1.0-2-g4f8925a

2) Ruby2.3.3のインストール

[myname@ip-10-0-1-86 ~]$ sudo yum install -y gcc
[myname@ip-10-0-1-86 ~]$ sudo yum install -y openssl-devel readline-devel zlib-devel
[myname@ip-10-0-1-86 ~]$ rbenv install 2.3.3

3) Nginxのインストール・設定

[myname@ip-10-0-1-86 ~]$ sudo yum install nginx
[myname@ip-10-0-1-86 ~]$ cd /etc/nginx/conf.d/
[myname@ip-10-0-1-86 conf.d]$ sudo touch local.conf
[myname@ip-10-0-1-86 conf.d]$ sudo vi local.conf

==================== local.conf ===================================

upstream unicorn {
  server unix:/tmp/unicorn.sock;
}

server {
  listen 80;
  server_name YOUR_IP_ADDRESS;

  access_log /var/log/nginx/sample_access.log;
  error_log /var/log/nginx/sample_error.log;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://unicorn;
  }
}

=================================================================

4) デプロイするディレクトリと権限の変更

[myname@ip-10-0-1-86 conf.d]$ cd /var/
[myname@ip-10-0-1-86 var]$ sudo mkdir www
[myname@ip-10-0-1-86 var]$ sudo chmod 777 www
[myname@ip-10-0-1-86 var]$ cd www
[myname@ip-10-0-1-86 www]$ mkdir qiitaApp
[myname@ip-10-0-1-86 www]$ cd qiitaApp
[myname@ip-10-0-1-86 qiitaApp]$ rbenv local 2.3.3

5) GitHubへの接続 SSHkey

[myname@ip-10-0-1-86 qiitaApp]$ cd 
[myname@ip-10-0-1-86 ~]$ ssh-keygen -t rsa
$ cat .ssh/id_rsa.pub
ssh-rsa abcdefghijklmnopqrstuvwxyz1234567890 ec2-user@ip-10-0-1-86

[ssh-rsa]から[ec2-user@ip-10-0-1-86]までをコピーしてGitHubのSSH keysにNewKeyとして追加する

5) その他必要なソフトウェアのインストール・設定

  • bundler
[myname@ip-10-0-1-86 ~]$ rbenv exec gem install bundler
[myname@ip-10-0-1-86 ~]$ rbenv rehash
  • Node.js
[myname@ip-10-0-1-86 ~]$ sudo yum install nodejs --enablerepo=epel
  • sqlite
[myname@ip-10-0-1-86 ~]$ sudo yum install sqlite-devel
  • mysql
[myname@ip-10-0-1-86 ~]$ sudo yum install mysql-devel

ローカルの設定

1) railsアプリの生成とgemのインストール

$ rails new qiitaApp
$ cd qiitaApp/
$ git init
$ vi Gemfile

============ Gemfile ===============

gem 'unicorn' #コメントアウトを解除する
gem 'mysql2'                                                                                                                                                                                                       
group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rbenv'
  gem 'capistrano3-unicorn'
end

===================================

$ bundle install
$ rails g scaffold user name:string age:integer
$ rake db:migrate

2) Capistranoの設定に必要なファイルの生成

$ bundle exec cap install STAGES=production #自動で下記のディレクトリとファイルを生成
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

3) Capfileの設定

$ vi Capfile #下記を追加
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

4) config/deploy.rbの設定

$ vi config/deploy.rb

lock "3.8.1"

set :application, "qiitaApp"
set :repo_url, "git@github.com:YOUR_GITHUB_ACCOUNT/qiitaApp.git"

set :rbenv_type, :user
set :rbenv_ruby, '2.3.3'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
set :rbenv_roles, :all

set :log_level, :warn 

# Default value for :linked_files is []
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')

# Default value for linked_dirs is []
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

# Default value for keep_releases is 5
set :keep_releases, 3

set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"

set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn.rb") }

namespace :deploy do
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
end

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end    

5) config/deploy/production.rbの設定

set :branch, 'master'

server 'EC2-IP-ADDRESS', user: 'ec2-user', roles: %w{app db web} ※

set :ssh_options, {
    keys: %w(~/.ssh/YOUR_EC2_KEY.pem),
    forward_agent: true,
    auth_methods: %w(publickey)
  }

※今回はEC2-userでログインする設定で進めていきます。
本来はデプロイユーザを設定して、そのユーザのみがデプロイ可能にすべきだと思います。

7) Unicornの設定

手動で config 配下に unicorn.rb を生成

$ cd config
$ touch unicorn.rb
$ vi unicorn.rb

========================== unicorn ==============================

APP_PATH   = "#{File.dirname(__FILE__)}/.." unless defined?(APP_PATH)
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
RAILS_ENV  = ENV['RAILS_ENV'] || 'development'

worker_processes 3

listen "/tmp/unicorn.sock"
pid "tmp/pids/unicorn.pid"

preload_app true

timeout 60
working_directory APP_PATH

# log
stderr_path "#{RAILS_ROOT}/log/unicorn_error.log"
stdout_path "#{RAILS_ROOT}/log/unicorn_access.log"

if GC.respond_to?(:copy_on_write_friendly=)
  GC.copy_on_write_friendly = true
end

before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = APP_PATH + "/Gemfile"
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH

    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

=================================================================

ここまでで各種設定に関しては終了です。
shared/config 配下のdatabase.ymlsecrets.yml は手動で作成します。
が残っていますが、こちらは後編で実施します。

最後にGitHubにここまでのコードをあげておきます。

$ git add .
$ git commit -m "first commint(任意)"
$ git remote add origin git@github.com:YOUR_GITHUB_ACCOUNT/qiitaApp.git
$ ssh-add ~/.ssh/id_rsa_github
$ git push origin master

前編は以上です。
後編ではデプロイチェック、デプロイ、ログ確認を実施します。

続きを読む

Setup Rails on unicorn on nginx on AWS EC2 linux

How to install Unicorn

1.Modify Gemfile contents

$ sudo vim ~/(sampleapp)/Gemfile

Add gem 'unicorn'

2. Install unicorn at the Gemfile’s directory

$ bundle install

3. Check installation of unicorn

$ bundle show unicorn
/home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/unicorn-5.3.0

4. Make “unicorn.rb” on /(sampleAppDirectory)/config and write following contents

unicorn.rb
  worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
  timeout 15
  preload_app true

  listen '/home/ec2-user/testApp/tmp/unicorn.sock' #{Railsアプリケーションのあるディレクトリ}/tmp/unicorn.sock
  pid    '/home/ec2-user/testApp/tmp/unicorn.pid' #{Railsアプリケーションのあるディレクトリ}/tmp/unicorn.pid

  before_fork do |server, worker|
    Signal.trap 'TERM' do
      puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
      Process.kill 'QUIT', Process.pid
    end

    defined?(ActiveRecord::Base) and
      ActiveRecord::Base.connection.disconnect!
  end

  after_fork do |server, worker|
    Signal.trap 'TERM' do
      puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
    end

    defined?(ActiveRecord::Base) and
      ActiveRecord::Base.establish_connection
  end

  stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
  stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

5. Execute following code and create rake file at your app directory

$ rails g task unicorn 
Running via Spring preloader in process 13140
      create  lib/tasks/unicorn.rake

6. Start unicorn and check unicorn starting

$ bundle exec unicorn_rails -c config/unicorn.rb 

*↑production環境かdeployment環境は選べる。Unicorn commands一覧参照

$ ps -ef | grep unicorn
ec2-user 12631 11576  0 08:09 pts/1    00:00:00 vim unicorn.rb
ec2-user 13547 13151  0 08:37 pts/5    00:00:01 unicorn master -c config/unicorn.rb                                                                                            
ec2-user 13582 13547  0 08:37 pts/5    00:00:00 unicorn worker[0] -c config/unicorn.rb                                                                                         
ec2-user 13584 13547  0 08:37 pts/5    00:00:00 unicorn worker[1] -c config/unicorn.rb                                                                                         
ec2-user 13587 13547  0 08:37 pts/5    00:00:00 unicorn worker[2] -c config/unicorn.rb                                                                                         
ec2-user 14665 13960  0 08:51 pts/3    00:00:00 grep --color=auto unicorn

Modify the setting of Nginx

1. Modify nginx.conf to rails.cof

nginx/1.10.2時点ではnginx.conf($ nginx -tでconfigファイルを確認できる)。nginx.confに以下のように書き換える。

nginx.conf
# For mor# e information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user ec2-user;
worker_processes 1;

events {
    worker_connections 1024;
}

http {
#    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
#                     '$status $body_bytes_sent "$http_referer" '
#                      '"$http_user_agent" "$http_x_forwarded_for"';

#   access_log  /home/ec2-user/testApp/access.log  main;

#    sendfile            on;
#    tcp_nopush          on;
#    tcp_nodelay         on;
#    keepalive_timeout   65;
#    types_hash_max_size 2048;

#    include             /etc/nginx/mime.types;
#    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
#   include /etc/nginx/conf.d/*.conf;

#   index   index.html index.htm;

    upstream unicorn {
      server  unix:/home/ec2-user/testApp/tmp/unicorn.sock; #/home/{ユーザ名}/{Railsアプリケーション>名}/tmp/unicorn.sock
    }

    server {
        listen       xx; #HTTP = 80
        server_name  xxx.xxx.xxx.xxx; #Your server ip-address or domain name

        access_log  /var/log/access.log;
        error_log   /var/log/error.log;

        root    /home/ec2-user/testApp/public; #/home/{ユーザ名}/{Railsアプリケーション名}/public
#       index   index.html;
        client_max_body_size 4G; #100m;
        error_page  404              /404.html;
        error_page  500 502 503 504  /500.html;
        try_files   $uri/index.html $uri @unicorn;

        location @unicorn {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_pass http://unicorn;
                }

        }

2. Reload nginx

$ sudo service nginx restart

3. Access your server address!!!

ここからはunicornのnginxに対するpermission deniedが出た時の処理

2017/04/23 06:45:40 [crit] 29912#0: *1 open()
"/var/lib/nginx/tmp/proxy/1/00/0000000001" failed (13: Permission
denied) while reading upstream, client: (clientserver), server:
(my-server), request: "GET / HTTP/1.1", upstream:
"http://unix:/pathTo/tmp/unicorn.sock:/", host:
"(my-server)"

上記エラーではnginx, tmpにおけるアクセス権限がnginxのみしかないので、アクセス権を解放する(下記アクセスは777としているが修正する必要あり)

$ sudo chmod 777 nginx
$ sudo chmod 777 tmp

続きを読む