とーますメモ

Ruby on Rails / Goなどの学習メモ

【Ansible】デプロイ完了通知をSlackに流す方法

すごい簡単。

1)Slackで通知を送るチャンネル作成

説明省略

2)Incoming Webhooksのトークンを取得

以下のページから。
https://my.slack.com/services/new/incoming-webhook/

1)で作成した、チャンネルを設定

3)タスク作成

roles/slack/tasks/main.yaml

---

- name: Send notification deploy completed message via Slack
  slack:
    token: TTTTTTTTT/BBBBBBBBB/ped3ksoaksajeafkafsa32sa
    msg: '{{ inventory_hostname }} deploy completed'
    color: good

後は、実行するだけ。

他にもチャンネルを設定、アイコン画像の設定など色々とオプションがある。
slack - Send Slack notifications — Ansible Documentation

【Ansible】MySQLのrootパスワードが変更できない

Ansibleはバージョン「2.2.0.0_2」を使用。
インストール先OSは、Ubuntu 16.04.

そして以下のコード実行し、rootパスワードを変更を試みたが、
更新されず、パスワード無しでログインできてしまう。

tasks/main.yml

- name: Install the mysql packages in Debian derivatives
  apt: name={{ item }} state=installed
  with_items:
    - mysql-server
    - python-mysqldb

- name: update mysql root password for root account
  mysql_user: name=root password={{ mysql_root_user_pass }} state=present

長々と調べた結果、以下のStackoverflowにたどり着いた。
Ansible Install MySql 5.7 - Set Root User Password - Stack Overflow

The gist of the problem had to do with mysql 5.7 using auth_socket for the root user when no password is provided

問題は、mysql5.7ではパスワードが設定されていないrootユーザに対して、auth_socketを使うことに起因している?
という解釈なのか。。。

解決方法は2つあって、

1)MySQLのdefconfを設定する

defconfとは、簡単に言うと「設定に関する処理を行ってくれるユティリティ」ということになるのだろうか。
By Wikipedia

MySQLでは、インストール時にパスワード設定を求められないようにするために
使用できる。
UbuntuでMySQLをインストールするときにパスワードの設定を求められないようにする - Qiita

同じことをAnsibleでやると以下のようになる。

tasks/main.yml

- name: Specify MySQL root password before installing
  debconf: name='mysql-server' question='mysql-server/root_password' value='{{mysql_root_user_pass | quote}}' vtype='password'

- name: Confirm MySQL root password before installing
  debconf: name='mysql-server' question='mysql-server/root_password_again' value='{{mysql_root_user_pass | quote}}' vtype='password'  

- name: Install the mysql packages in Debian derivatives
  apt: name={{ item }} state=installed
  with_items:
    - mysql-server
    - python-mysqldb

- name: update mysql root password for root account
  mysql_user: name=root password={{ mysql_root_user_pass }} state=present

2)Ansibleのバージョンを2.2.1以上にする

これが一番簡単。

【Ansible】Rbenvで「rbenv: command not found」というエラーが出た場合の対策

以下のサイトさんが詳しい。
www.bunkei-programmer.net

要は、ansible経由の場合は、/bin/shシェルが使用され
.bash_profileや.bashrcを読み込む、/bin/bashシェルが使用されないため
発生する現象ということっぽい。

引用すると

1)bashコマンドを「-l」オプション付きで実行する方法

- name: install ruby
  shell: bash -lc "rbenv install {{ ruby_version }}"

2)ansible.cfgにexecutableを追加する方法

[defaults]
executable = /bin/bash -l

追記すると、もう一個やり方があって、
以下のようも書けるっぽい。

- name: install ruby
  shell: sudo -i rbenv install {{ ruby_version }}

ユーザを指定するなら以下
例)rootを指定

- name: install ruby
  shell: sudo -iu root rbenv install {{ ruby_version }}

[参考]
ruby - Install Bundler gem using Ansible - Stack Overflow

【ufw】Ubuntuで簡単にFirewallを設定できるufwを試す。

自分用メモ。

すごい簡単に設定できる。
DigitaloceanのUbuntu16.04イメージに既に入っていたので
ルールを追加して、有効化するだけで使えた。

現在のステータスを確認

$ ufw status

デフォルトの通信ルール

通信拒否方法①

$ ufw default DENY

通信拒否方法②

$ ufw default REJECT

があるが、外部から脆弱性テストを行われたときに
すぐ結果を返してしまうREJECTは、あまりよろしくないので
①を設定しておくのが良さげ。

基本の通信を許可

$ ufw allow 22
$ ufw allow 80
$ ufw allow 443

ロギング機能をON

$ ufw logging on

ログは、「/var/log/ufw.log」にある。

【Ansible】「/usr/bin/python: not found」エラーに対する処理

Ubuntu 16.04ではpython3が入っているが、
pythonが入っていないため発生するエラー。

以下のサイトが詳しい。
【小ネタ】Python3がインストールされているOSにansibleを実行する | Developers.IO
ansible pythonが無い場合の対処 - vague memory

自分の場合、Ubuntuに現段階では、phthon3には対応していないと思われるufwを
使用したかったため、サーバにpythonが存在しない場合に、pythonをインストールする
コードを以下のようにplaybookに追加した。

gather_facts: False
pre_tasks:
  - name: Install python for Ansible
    raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
    register: output
    changed_when: output.stdout != ""
    tags: always
  - setup: # aka gather_facts

[参考]
Ansible fails with /bin/sh: 1: /usr/bin/python: not found - Stack Overflow
FS#52267 : Ansible package is running on python 3 which breaks some uncompatible modules (like ufw)

【Rails】Rails5で文字列の"true"や"false"をBooleanにキャストする方法

Rails5では以下のように書ける。

ActiveRecord::Type::Boolean.new.cast(0) # false
ActiveRecord::Type::Boolean.new.cast(1) # true
ActiveRecord::Type::Boolean.new.cast('0') # false
ActiveRecord::Type::Boolean.new.cast('1') # true
ActiveRecord::Type::Boolean.new.cast(false) # false
ActiveRecord::Type::Boolean.new.cast(true) # true
ActiveRecord::Type::Boolean.new.cast('false') # false
ActiveRecord::Type::Boolean.new.cast('true') # true
ActiveRecord::Type::Boolean.new.cast(nil) # nil

[参考]
check_boxによって送られてくるパラメータを真偽値として扱う方法 - Qiita

【Rails】Sidekiqを使用してみた。

自分用メモ

ActiveJob経由か、直接使うかは
以下の記事で詳細を書いた。
thoames.hatenadiary.jp

自分の場合は、フルにSidekiqの機能を使用したかったため
直接Sidekiqを使用する」方法を取る。

インストール

Gemfile

gem 'sidekiq'
gem 'redis-namespace'

[注意] Redisの設定にnamespaceを設定している場合、
'redis-namespace'のgemをインストールしていいないと、
以下のエラーが出る。

ERROR: Your Redis configuration uses the namespace 'xxxxxxxxx'
but the redis-namespace gem is not included in the Gemfile.
Add the gem to your Gemfile to continue using a namespace. 
Otherwise, remove the namespace parameter.

[参考]
Sidekiq - DesignAssembler

実行

設定ファイルを読み込んで起動する場合

$ bundle exec sidekiq -C config/sidekiq.yml

ベストプラクティス

以下のようにオブジェクトを渡した後、キューが実行される前に
そのオブジェクトが変わった場合、
意図したものと違う内容が実行されてしまう可能性がある。
またRedisにデータを保存する時、引数の情報がJSONとして保存される。
(内部ではRedisに保存するときに、JSON.dumpを使用し、
Redisからデータを参照するときにJSON.loadを使用している)

このため、perform_asyncに渡す引数は必ずJSONとして保存できる
「string, integer, float, boolean, null, array and hash」のいずれかでなければならない。
シンボルやオブジェクト(dateやtimeなども)は引数にしてはいけない。

そのため、処理を特定できる識別子(IDなど)を引数として設定し、
キュー処理の内部で、IDを用い処理を行うことが推奨されている。

✕: 悪い例

quote = Quote.find(quote_id)
SomeWorker.perform_async(quote)

○: 良い例

SomeWorker.perform_async(quote_id)

Best Practices · mperham/sidekiq Wiki · GitHub

リトライ

例外を発生させれば、自動的にリトライが走る。

class HogeJob < ActiveJob::Base
  queue_as :default

  def perform(hoge_id:)
    raise "error" # 明示的に例外投げる
  end
end

リトライさせたくない場合の設定方法は2種類がある。

① retry: 0
② retry: false

設定例)

...
sidekiq_options queue: :xxx, retry: 0
# または
sidekiq_options queue: :xxx, retry: false
...

違いは①の場合は、例外が発生した場合、Sidekiq画面の死亡タブで
内容を確認でき手動で再試行が可能だが、②の場合は死亡タブで確認できない。

再試行する必要がなく、死亡したジョブ履歴を追う必要が無い場合を除き
大体の場合、①の方法を選択するほうが良い。

各workerに設定したくない場合はsidekiq.rbに以下のように設定すれば
全体に設定される。

config/initializers/sidekiq.rb

...
Sidekiq.default_worker_options = { 'retry' => true }
# または
Sidekiq.default_worker_options = { 'retry' => 0 }
...

[参考]
Sidekiq で最大回数リトライ後に失敗した場合出すログに例外のバックトレースを含める - Qiita
sidekiqのリトライ回数は26回以上を指定できる - PartyIX
Delayed::Job で絶対にやっておいた方がいいたった1つの設定
sidekiqにretryしてほしくないが、死亡には入って手動retry出来るようにしたい場合

バックトレース

例外発生時に、その例外のbacktraceログも残したい場合は以下の設定を行う。

sidekiq_options backtrace: true

ただし、大量にJOBが失敗するなど、かなりのbacktraceログが発生する可能性がある場合は
Redisに割り当てるメモリを増やすか、backtraceログの出力行を設定する。
出力行は上から数えた行数を設定する。

例)上から20行までを出力する設定

sidekiq_options backtrace: 20

各workerに設定したくない場合はsidekiq.rbに以下のように設定すれば
全体に設定される。

config/initializers/sidekiq.rb

...
Sidekiq.default_worker_options = { 'backtrace' => true }
# または
Sidekiq.default_worker_options = { 'backtrace' => 20 }
...

死亡直前のログ出力

各workerで設定する場合

...
include Sidekiq::Worker

sidekiq_retries_exhausted do |msg, e|
  Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
end
...

一括で設定する場合(config/initializers/sidekiq.rb)

Sidekiq.configure_server do |config|
  config.default_retries_exhausted = -> (job, ex) do
    Sidekiq.logger.warn "#{job['class']} job is now dead: args(#{job['args']}) => msg(#{job['error_message']})"
  end
end

[参考]
Sidekiq で最大回数リトライ後に失敗した場合出すログに例外のバックトレースを含める - Qiita

死亡時にSlack通知

Slack通知にはslack-notifierを使用する。
詳細は以下のページが詳しい。

slack-notifierでrailsからSlackへ簡単にメッセージを送る - Qiita

config.default_retries_exhausted内に設定を追記すれば良い。

config.default_retries_exhausted = -> (job, ex) do
  message = "#{job['class']} job is now dead: args(#{job['args']}) => msg(#{job['error_message']})"
  Slack::Notifier.new(Settings[:sidekiq][:slack_notifier_url]).ping(message)
  Sidekiq.logger.warn message
end

ベーシック認証

require 'sidekiq/web'

Sidekiq::Web.use Rack::Auth::Basic do |username, password|
  username == 'basicuser' && password == 'basicpass'
end
mount Sidekiq::Web => '/sidekiq'

プラグイン

sidekiq-failures

GitHub - mhfs/sidekiq-failures: Keep track of Sidekiq failed jobs

失敗したジョブの詳細をWebUIで見ることができる。
デフォルトだと最新の失敗を、1000個まで確認できる。

以下のヘルパーメソッドは便利そう

Sidekiq::Failures.count
Sidekiq::Failures.reset_failures
sidekiq-history

GitHub - russ/sidekiq-history: Sidekiq History

ダッシュボードの拡張。
種類に関係なく全ジョブの履歴ログを見ることができるようになる。
デフォルトだと最新の履歴ログを、1000個まで確認できる。

sidekiq-statistic

GitHub - davydovanton/sidekiq-statistic: See statistic about your workers

ダッシュボードの拡張。
解析用画面がWebUIに追加される。
グラフで成功、失敗したJobの確認ができたり
解析用のAPIが使えるようになる。

sidekiq-unique-jobs

GitHub - mhenrixon/sidekiq-unique-jobs: The missing unique jobs in sidekiq

The job will be unique for the number of seconds configured (default 30 minutes) or until the job has been completed.
Thus, the job will be unique for the shorter of the two.

同じJobが複数登録された場合、それを1つのJobにするっぽい。
デフォルトだと30分。

capistrano-sidekiq

GitHub - seuros/capistrano-sidekiq: Sidekiq integration for Capistrano

capistranoを使用している場合は、デプロイ時に Sidekiq がロードしているコードが更新されないため、
下記の設定を追記して、デプロイと同時に再起動されるように設定します。

config/Capfile

require 'capistrano/sidekiq'

config/deploy.rb

set :sidekiq_role, :web

By Railsで非同期処理を行える「Sidekiq」

[参考]
Sidekiqで非業の死を遂げたキューを知る方法 - Qiita
Railsで非同期処理:キュー。Sidekiq(+ActiveJob)がResqueよりも、とても簡単便利。 - Qiita
capistrano-sidekiqを使うのをやめる - Qiita