PONCOTSU

インフラ領域を主にあれしてます

RubyスクリプトからMySQLにログインする

ActiveRecord上からDBに接続すると実際にどんなふうにDB操作をプログラムからやれるものかのかイメージしづらくなる。そこで今回はRubyのプログラムを書いて、MySQLにログインするためのスクリプトを書いてみる。

対象読者

  • ある程度Rubyを書いたことがある
  • サーバ(又はローカル)にMySQLをインストールしたことがある
  • 簡単なクエリを書いたことがある
  • Dockerを使ったことがある

構成

  • ローカル:Rubyコードをつくる・実行する
  • MySQLコンテナ:Dockerを使ってMySQLサーバとしてコンテナをつくる

こんな感じ。 RDSとか使ってもよいのだけども、検証のためなので、擬似外部サーバとしてDockerを採用した。

STEP0: 前提とする環境

  • ローカル
    • mysqlインストール済
    • rubyが動く状態
    • gem install ruby-mysql しておく
    • dockerが動く状態

STEP1: MySQLコンテナをつくる

Docker使っているならわりと簡単。

docker pull mysql
docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=root -d -p 33066:3306 mysql

これでMySQLサーバが作成された。 これでコンテナ上にもmysqlのプロセスも立ち上がっているはず。

STEP2: ローカルからコンテナ上のMySQLにログインしてみる

mysql -h 0.0.0.0 -uroot -p
# パスワードは root
mysql >

無事、mysqlにログインできたらOK.

STEP3: コンテナ上で少し下準備

コンテナ上のmysqlにログインしたまま、下準備をする。 具体的には、データベース・テーブルの作成を行ないます。

CREATE DATABASE test;
mysql> CREATE TABLE users(id int, name varchar(50));
mysql> use test;
mysql> show tables;
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| users          |
+----------------+
1 row in set (0.00 sec)

mysql> DESC users;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  |     | NULL    |       |
| name  | varchar(50) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

こんな感じのデータベースになればOK.

STEP4: Rubyスクリプトを書く

test.rb

require 'mysql'
connection = Mysql::connect("0.0.0.0", "root", "mysql", "test", 33066)

# 文字コードをUTF8に設定
connection.query("set character set utf8")

# DBに問い合わせ

# 挿入するデータ
data= {
1: 'hoge',
2: 'fuga'
}

# 用意したデータの分だけINSERT文を実行
data.each do |id, name|
  connection.query("INSERT INTO test.users(id, name) VALUES(#{id}, '#{name}')");
end

# SELECT文の結果を取得
rs = connection.query("SELECT * FROM users")

# 検索結果を表示
rs.each do |r|
  puts r.join ", "
end

# コネクションを閉じる
connection.close

実行してみる

書いたスクリプトを実行してみる。

[localhost]$ ruby test.rb
1, hoge
2, fuga

無事、RubyスクリプトからMySQLにログインし、クエリを叩くことができるようになりました。

利用できるデバイス情報を把握する lsblkコマンド

ハードデバイスについて勉強してみた。

Linuxはデバイスファイルを通してデバイスを利用することができる

すべてをファイルとして扱う思想のLinuxでは、接続されたデバイスもファイルを通して利用することになっている。 そのホストとデバイスの入出力を扱うための特殊なファイルをデバイスファイルとよぶ。 IDEなのかSCSI系なのか、ハードディスクによって多少仕様は変わるが、ここでは割愛する。

ホストからどんなデバイスを利用することができるのか確認してみる

lsblkコマンド を使えば、対象ホストから利用できるデバイスの詳細情報を確認することができる。

root@ip-xxx-xxx-xxx-xxx:/# lsblk -l
NAME  MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda  202:0    0   8G  0 disk
xvda1 202:1    0   8G  0 part /

上記の出力について、少し説明。

項目 説明
NAME バイス
MAJ:MIN バイス番号(メジャー:マイナー)
RM リムーバブルディスクかどうか
SIZE 容量
RO 読み取り(ReadOnly)専用か否か
TYPE バイスの種類
MOUTPOINT マウント先

さらに詳細を説明

気になった項目に絞って調べてみた

TYPE

主なデバイスの種類として以下の3つがあるようです。

バイスの種類 説明
disk 物理ドライブ
part パーティション
lvm Logical Volume Manager。複数のハード・ディスクやパーティションにまたがった記憶領域を一つの論理的なディスクとして扱うことのできるディスク管理機能で作られた論理ボリューム

なぜRMの欄が存在するか?

そもそもRMとは、記録メディアを取り出して交換できる記憶装置のことを指す。 ストレージ運用は、内臓ハードディスクか取り外し可能なハードディスクなのかによって大きく変わるため、表示されるものだと推測される。

結局のところ、lsblkコマンドでどんなことができるのか?

使い方:
 lsblk [オプション] [<デバイス> ...]

オプション:
 -a, --all            すべてのデバイスを表示します
 -b, --bytes          可読性の高い形式ではなく、バイト単位でサイズを表示します
 -d, --nodeps         スレーブデバイスやホルダーを表示しません
 -D, --discard        discard 関連の機能を表示します
 -e, --exclude <一覧> メジャー番号を利用して、除外するデバイスを指定します (既定値: RAM ディスク)
 -f, --fs             ファイルシステムに関する情報を出力します
 -i, --ascii          ASCII 文字のみを使用します
 -I, --include <一覧> 指定したメジャー番号のデバイスのみを表示します
 -l, --list           一覧形式で出力します
 -m, --perms          パーミッションに関する情報を出力します
 -n, --noheadings     ヘッダを表示しないようにします
 -o, --output <list>  出力する列を指定します
 -O, --output-all     output all columns
 -p, --paths          完全なデバイスパスを表示します
 -P, --pairs          キー="値" の出力形式を使用します
 -r, --raw            加工を行なわない出力形式を使用します
 -s, --inverse        依存関係を逆転します
 -S, --scsi           SCSI デバイスに関する情報を出力します
 -t, --topology       トポロジに関する情報を出力します
 -x, --sort <column>  sort output by <column>

 -h, --help     このヘルプを表示して終了します
 -V, --version  バージョン情報を表示して終了します

利用可能な列 (--output で指定します):
        NAME  デバイス名
       KNAME  カーネル内部デバイス名
     MAJ:MIN  メジャー:マイナーデバイス番号
      FSTYPE  ファイルシステムの種類
  MOUNTPOINT  マウントされている場所
       LABEL  ファイルシステムのラベル
        UUID  ファイルシステムの UUID
    PARTTYPE  partition type UUID
   PARTLABEL  パーティションのラベル
    PARTUUID  パーティション UUID
   PARTFLAGS  パーティションフラグ
          RA  デバイスの先読み
          RO  読み込み専用デバイス
          RM  リムーバブルデバイス
       MODEL  デバイス識別子
      SERIAL  ディスクのシリアル番号
        SIZE  デバイスのサイズ
       STATE  デバイスの状態
       OWNER  ユーザ名
       GROUP  グループ名
        MODE  デバイスノードのパーミッション
   ALIGNMENT  アライメントオフセット
      MIN-IO  最小 I/O サイズ
      OPT-IO  最適 I/O サイズ
     PHY-SEC  物理セクタサイズ
     LOG-SEC  論理セクタサイズ
        ROTA  ローテーションデバイス
       SCHED  I/O スケジューラ名
     RQ-SIZE  要求キューサイズ
        TYPE  デバイスの種類
    DISC-ALN  discard アライメントオフセット
   DISC-GRAN  discard 粒度
    DISC-MAX  discard 最大バイト
   DISC-ZERO  discard ゼロデータ
       WSAME  write-same 最大バイト
         WWN  ユニークなストレージ識別子
        RAND  乱数シードへの追加
      PKNAME  親のカーネル内部デバイス名
        HCTL  SCSI 向けのホスト:チャンネル:ターゲット:LUN
        TRAN  デバイス伝送タイプ
         REV  デバイスのリビジョン
      VENDOR  デバイスの製造元
````

macosでJVMのスレッドダンプを取得する

JVMのこと、詳しくなりたくって、でも何もできずにいる自分がただそこにいるだけで、悔しくて泣きたくて。
そんな気持ちを最近持ったので、勉強しはじめた。

とりあえずjavaをインストールする

ORACLEのインストールガイドを読むのが一番。
MacでのJavaのインストール方法

インストール完了後、こんな感じでバージョンを確認できたらよさげ。

$java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

javaのプログラムを起動する

とりあえず簡単なプログラムを起動させて、立ち上がっているプロセスを確認したい。
ただ、ずっと起動しぱなっしのプロセスにしてあげる必要があるので、BufferedReader というやつを使って、入力完了するまでプロセスが死なないようにした。

import java.io.*;
public class HelloWorld {
  public static void main (String[] args) throws IOException
    {
        InputStreamReader is = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(is);
        System.out.println("何か入力してください.");
        String str = br.readLine();
        System.out.println(str + "が入力されました.");
    }
}

HelloWorld.java として保存して、 javac HelloWorld.java , java HelloWorld を実行すると実行される。

javaプロセス起動中にスレッドダンプを取得

プログラムを実行すると、 何か入力してください. と出たまま入力せずに別のコンソールから起動中のプロセスを確認する。

$ps aux | grep java
user           34577   0.0  0.0  2432804    776 s021  S+    2:27AM   0:00.00 grep java
user           34573   0.0  0.3  6026488  22852 s019  S+    2:27AM   0:00.16 /usr/bin/java HelloWorld

ちゃんと立ち上がったままになってて良い感じ。

次にスレッドダンプを取得。

$jps
34582 Jps
34573 HelloWorld

jpsコマンドで、pidに紐づいたjavaの実行プログラムがリストで取れるっぽい。
さらに jstack コマンドでpidを指定するとそのプロセスのスレッドダンプを出力できる。

$jstack 34420
2017-05-17 02:13:46
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"Attach Listener" #9 daemon prio=9 os_prio=31 tid=0x00007fc815083800 nid=0x1307 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #8 daemon prio=9 os_prio=31 tid=0x00007fc815025800 nid=0x4903 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #7 daemon prio=9 os_prio=31 tid=0x00007fc816008800 nid=0x4703 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=31 tid=0x00007fc81582c800 nid=0x4503 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=31 tid=0x00007fc815871800 nid=0x4303 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fc815871000 nid=0x4103 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fc815857800 nid=0x3103 in Object.wait() [0x000070000dd8e000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x0000000795588ec8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
...

あとはよさげに

jstack 34420 > hoge.log

とかしてあげるとよい。

なお、スレッドダンプの読み方も調べてみたけど語るには知識がなさすぎるので、参考になった記事を共有するに留めておくことにします。

enk.hatenablog.com

Java書かないのでまったくここらへんのことわからないけど、少しずつこうやって知見を貯めていきたい。

Infrastructure as Codeの価値・原則・プラクティス・評価軸

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code本の読書まとめ。
1周目は理解仕切れていないところ多くて、2周目でなんとなーくわかってきて、3周目の今、自分なりにまとめていこうと思いまして、記事にしてます。
ほとんどの項目がまんま書かれていますが、いまいちわかっていなかったとこは自分なりのメモを残すことにした。

価値

  • 変化を支えられるインフラをつくれる
  • システムの更新を日常のものにできる
  • プロダクト開発者地震がリソースのメンテナンスを行える
  • 障害が起きてもすぐに対応できる

原則

  • 簡単に再現できるシステムであること:ほとんどの作業が自動化されている状態であること
  • 使い捨てにできるシステムであること:変化に合わせて今あるものを除却/追加しやすい状態であること
  • 統一的なシステムであること:同じ役割をもつサーバがほとんど同じ状態であること
  • 反復できるプロセスであること:誰がいつやっても同じ状態をつくれる状態であること

プラクティス

  • 定義ファイルを使いましょう
  • 自己記述システムでプロセスを担保しましょう
  • VCSを使いましょう

評価軸

  • 労力をかけずにインフラ開発・運用ができているか
  • 常にサーバは最新の状態かつ統一性が担保されているか
  • CI/CDの仕組みが整っており、開発者がそれを容易に実行できるか
  • 営業中にデプロイできるような状態か
  • MTTRをKPIとして改善に取り組めているか

nginxの負荷分散手法(1) 重み付け

nginxでは、リバースプロキシ機能を使って、負荷分散を行なうことができる。
主なやり方は3つ。

  1. 重み付けによる負荷分散
  2. 稼働系と待機系
  3. 接続数による負荷分散

今回は、 1. 重み付けによる負荷分散 について。
設定方法は簡単。
upstreamコンテキストでまとめた複数のバックエンドサーバにweightオプションと重みパラメータをつけるだけ。

upstream backend {
  server xxx.xxx.xxx.xxx weight=3;
  server yyy.yyy.yyy.yyy weight=2;
  server zzz.zzz.zzz.zzz weight=1;
}

この設定で、

  • xxx.xxx.xxx.xxx サーバは 全アクセスの3/6 の割合を捌くこととなる
  • yyy.yyy.yyy.yyy サーバは 全アクセスの2/6 の割合を捌くこととなる
  • zzz.zzz.zzz.zzz サーバは 全アクセスの1/6 の割合を捌くこととなる

サーキットブレイカーパターン:連続して起こる同様の障害への対応策

障害が起こりにくい・万が一、障害が起きた時にシステム側で早急な対応をするような仕組みを勉強しております。そこでサーキットブレイカー。

サーキットブレイカーパターンとはなにか

電子回路にも使われている遮断機が主な概念になっているパターン。何かしらのReq/Resに対して、良くない事象(つまり障害)が頻発すると「あ、これはやばいから一旦この導線をオフにしていこう」という仕組みのことを指します。リモートコールはインメモリコールと比べ、いくつか障害になりえる要素が多く、考慮が必要です。例えば、リモートコールはそもそも受信失敗しやすかったり、サプライヤー側からのレスポンスが頻繁にタイムアウトしてしまったりすることがあります。原因がリモートにある以上、対処がしにくく、かつ一度起きてしまったら、連続的に起こりうるような障害対応のために生まれたのがサーキットブレイカーパターンです。

仕組みと処理の流れ

  1. 中間層にサーキットブレイカーを設けます(処理の流れの中に組み込めばOK)
  2. クライアントがリクエストを投げると、そのリクエストはサーキットブレイカーを介してサプライヤー(リモートにあるサーバ側)に向かいます
  3. サプライヤー側になんの問題もなければサーキットブレイカーもそのまんまクライアントにレスポンスを渡します(正常系)
  4. もしサプライヤー側で処理が滞っていたり、そもそも何かしらの問題でサプライヤー側にリクエストが届いていなかったりしてレスポンスがなかったらサーキットブレイカーはその障害発生数を記録
  5. サーキットブレイカーで設定している閾値に達してしまうと、サーキットブレイカーは自身の処理を切り替え(これをトリップというらしい)、クライアント側へ早急にエラーメッセージを配信するようにします(この時点で、サプライヤーへの通信をしなくなる)。
  6. 妥当だと思われる障害復旧時間を設定しておけば、サーキットブレイカーは自己復旧を行ない、サプライヤー側へ疎通を図ります。問題がなさそうであれば、2の状態に戻ります。

f:id:tacmac:20160919212741p:plain

なお、サーキットブレイカーはこのような状態を切り替えながら(トリップしながら)、障害検知・切り替え・復旧を行なっております。 f:id:tacmac:20160919213752p:plain
Closedは電子回路でいうと電流が流れている状態なので正常系処理をしている状態。Openは導線を断ち切り、電流が止まっている状態なので「今サプライヤー側でなんか知らないけどよくない事態が起きてるのでそのリクエスト受け付けられないんです」とエラーメッセージを返している状態。HalfOpenがプログラム内で設定した時間間隔のもとサプライヤーへヘルスチェックしに行き、Closedに戻せるか確認している状態を意味します。

サーキットブレイカーの長所

自分らでは障害が起きても、根本的な対応が取れない外部サービスと連携している場合、導入する意味があるように思えます。一度Openになると、サプライヤーまでの通信は切られるので、ユーザーに時間的なストレスを感じさせないという点で、早急にエラーを返すことができるのがメリットだと思います。

ソース元: martinfowler.com

RailsでURLの末尾にスラッシュ(trailing_slash)をつける方法

SEO要件のためにURLの末尾に/(スラッシュ)をつけることがあります。静的ファイルや少ないページ数のサイトであれば愚直にスラッシュつける設定をやっていけばよさげですが、Railsのようなフレームワークで実現するにはどうしたらよいでしょうか。機会があったのでやってみました。

trailing_slash: を使う

# routes.rb

Rails.application.routes.draw do
  get '/items/:id', to: 'items#show', trailing_slash: true, as: 'item_detail'
end

とすると、 item_detail_pathをクリックした先のURLがしっかりとhttp:example.com/items/22/といった感じで末尾にスラッシュが付加されるようになります。 ちなみに上記は各ルーティングへの個別設定なのですが、アプリケーション全体で、末尾スラッシュ(これをtrailing_slashという)をつけたい場合は

# config/application.rb
class Application < Rails::Application
    config.action_controller.default_url_options = { :trailing_slash => true }
end

とすると、アプリケーションで発行するURL全体にtrailing_slashが付加される。

ルーティングオプションのtrailing_slashで賄えないこと

それは、直接URLを叩きにいった場合、末尾スラッシュ追加処理が行なわれないということ。

# routes.rb

Rails.application.routes.draw do
  get '/items/:id', to: 'items#show', trailing_slash: true, as: 'item_detail'
end

と設定した上で、例えばhttp://example.com/items/45を叩くとhttp://example.com/items/45のままです。http://example.com/items/45/とはなりません。カノニカルでtrailing_slashされたURLをセットしていても検索エンジンによっては処理が変わりうるので、せっかくSEO対策のためにtrailing_slashしたにもかかわらず不安が残ります。

結論:末尾スラッシュがなければ、末尾スラッシュ付きのURLリダイレクトさせればよい

先にコードを載せておきます。

# ApplicationController
def trailing_slash
    redirect_to "#{request.original_url}/" unless request.original_url.match(/\/$/)
end
# ItemsController
class ItemsController < ApplicationController
    before_filter :trailing_slash, only: [:show]
...
end

routes.rbの中で完結させるのは難しいので、コントローラ層で処理を書いてあげる。ApplicationControllerを読み込むタイミングでもしURLの末尾にスラッシュがないのであれば、スラッシュ付きにURLを変えてリダイレクトさせるとよい。これでroutes.rbを汚さずとも末尾スラッシュ追加ができるようになる。また、SEO要件としては追加したいアクション(path)が決まっている場合もありますね。そのときは上記コード例のようにbefore_filterで末尾スラッシュを付加させたいアクションを指定してあげるとよいです。