【Java SE 8】java.util.function 内の関数型インターフェースを似たような役割ごとにまとめてみた

概要

ラムダ式を扱うのに欠かせない java.util.function (Java Platform SE 8) 内の関数型インターフェースについてまとめます。

個人的には基本となる4つの関数型インターフェースとそのサブクラス、引数を2つ受け取るように特殊化されたものだけ覚えておけばいいと思っています。

プリミティブ特殊化されたものは、数も多いのでそんな物あるんだ程度に頭の片隅に残しておき、使いたくなったら調べながら使えばいいと思っています。

沢山あるので似たような役割ごとにまとめてみました。

基本となる4つの関数型インターフェース

Function<T, R>

T 型の引数を受け取って、R 型の値を返すインターフェースです。

抽象メソッドは R apply (T t) です。

static メソッドには入力値をそのまま出力する identity( ) があります。

インスタンス・メソッドには合成関数を作成する andThen( )compose( ) があります。

function1.andThen( function2 ) は function1 の後に引数に指定した function2 を実行します。

function1.compose( function2 ) は引数に指定した function2 を function1 より先に実行します。

Consumer<T>

T 型の引数を受け取って、何も値を返さない「消費者」を意味するインターフェースです。

抽象メソッドは void accept (T t) です。

static メソッドはありません。

インスタンス・メソッドに Function 同様に合成関数を作成する andThen( ) があります。

consumer1.andThen( consumer2 ) は consumer1 の後に引数に指定した consumer2 を実行します。

Supplier<T>

引数を何も受け取らず、T 型の値を返す「供給者」を意味するインターフェースです。

抽象メソッドは T get ( ) です。

その他に、static メソッドもインスタンス・メソッドも用意されていません。

Predicate<T>

T 型の引数を受け取って boolean 型の値を返す「断定」を意味するインターフェースです。

抽象メソッドは boolean test (T t) です。

static メソッドには値を比較する isEquals( ) があります。

インスタンス・メソッドには論理積を取る and( ) 論理和を取る or( ) 論理否定を取る negate( ) があります。

サンプルコード

基本となる 4 つの関数型インターフェースの抽象メソッドの使い方を示します。

関数型インターフェース 抽象メソッド
Function<T, R> R apply (T t)
Consumer<T> void accept (T t)
Supplier<T> T get ( )
Predicate<T> boolean test (T t)
import java.util.function.*;

public class Sample {
    public static void main(String[] args) {
        String message = "Hello";
// Function<T, R> Function<String, Integer> function = str -> str.length(); System.out.println(function.apply(message)); // 5
// Consumer<T> Consumer consumer = str -> System.out.println(str); consumer.accept(message); // Hello
// Supplier<T> Supplier supplier = () -> message + " World"; System.out.println(supplier.get()); // Hello World
// Predicate<T> Predicate predicate = str -> str.length() > 5; System.out.println(predicate.test(message)); // false } }

関数型インターフェースの特殊化

引数を2つ受け取る

基本となる関数型インターフェースの引数を2つ受け取るように特殊化されたものも存在します。

なお、Supplier<T> は引数を何も取らないインターフェースのため、引数を2つ取るように特殊化されたものは存在しません。

  • BiFunction<T, U, R>
    抽象メソッドは R apply (T t, U u)

  • BiConsumer<T, U>
    抽象メソッドは void accept (T t, U u)

  • BiPredicate<T, U>
    抽象メソッドは boolean test (T t, U u)

関数型インターフェースのサブクラス

基本となる関数型インターフェースを継承したインターフェースも存在します。

ただし、Function<T, R> か BiFunction<T, U, R> がベースとなっており、Consumer<T>, Supplier<T>, Predicate<T> を継承したインターフェースは存在しません。

UnaryOperation<T>

Function インターフェースのサブクラスです。

public interface UnaryOperation<T> extends Function<T, T>

extends 部分を見ると、前述した Function<T, R> が Function<T, T> となっています。

これは、引数で受け取った型と同じ型の値を返すと言う意味になります。

抽象メソッドは T apply (T t) です。

Function インターフェースを継承しているので andThen( ), compose( ), identity( ) も同様に使えます。

BinaryOperator<T>

BiFunction インターフェースのサブクラスです。

public interface BinaryOperator<T> extends BiFunction<T, T, T>

extends 部分を見ると、前述した BiFunction<T, U, R> が BiFunction<T, T, T> となっています。

これは、引数2つの型と返り値の型が全てが同じ型と言う意味になります。

抽象メソッドは T apply (T t, T t) です。

BiFunction インターフェースを継承しているので andThen( ) も同様に使えます。

static メソッドで引数で受け取った2つの値のうち、小さい方を返す minBy( ) と大きい方を返す maxBy( ) もあります。

関数型インターフェースのプリミティブ特殊化

Int, Long, Double のプリミティブ型の引数を受け取るように特殊化されたものも存在します。

Supplier<T> は引数を何も受け取らないインターフェースのため、プリミティブ型の引数を受け取るように特殊化されたものは存在しません。

Int 型の引数を受け取る

  • IntFunction<R>
    抽象メソッドは R apply (int value)
    返り値の型 R は指定できます
  • IntConsumer
    抽象メソッドは void accept (int value)
  • IntPredicate
    抽象メソッドは boolean test (int value)

Long 型の引数を受け取る

  • LongFunction<R>
    抽象メソッドは R apply (long value)
    返り値の型 R は指定できます
  • LongConsumer
    抽象メソッドは void accept (long value)
  • LongPredicate
    抽象メソッドは boolean test (long value)

Double 型の引数を受け取る

  • DoubleFunction<R>
    抽象メソッドは R apply (double value)
    返り値の型 R は指定できます
  • DoubleConsumer
    抽象メソッドは void accept (double value)
  • DoublePredicate
    抽象メソッドは boolean test (double value)

Supplier<T> は返り値のプリミティブ型を指定

Supplier<T> は引数でなく、返り値がプリミティブ型を返すように特殊化されたものが存在します。

抽象メソッドは get ( ) ではなく、getAsXXX ( ) となっています。

XXX は返り値の型となるプリミティブ型を表しています。

  • IntSupplier
    抽象メソッドは int getAsInt( )
  • LongSupplier
    抽象メソッドは long getAsLong ( )
  • DoubleSupplier
    抽象メソッドは double getAsDouble ( )
  • BooleanSupplier
    抽象メソッドは boolean getAsBoolean ( )

Function<T> で返り値のプリミティブ型を指定 

Supplier<T> 以外にも返り値のプリミティブ型を指定したインターフェースがあります。

Supplier<T> 以外と言いましたが、Consumer<T> は返り値の無いインターフェースですし、Predicate<T> は返り値が Boolean 型と決まっています。

なので、実質 Function<T, R> とBiFunction<T, U, R> が対象となります。

T 型の引数や U 型の引数を受け取って決められたプリミティブ型の値を返すので、インターフェース名が ToInt 〜、ToLong〜、ToDouble〜 と言う名前になっています。

  • ToIntFunction<T>
    抽象メソッドは int applyAsInt (T t)
  • ToLongFunction<T>
    抽象メソッドは int applyAsLong (T t)
  • ToDoubleFunction<T>
    抽象メソッドは int applyAsDouble (T t)
  • ToIntBiFunction<T, U>
    抽象メソッドは int applyAsInt (T t, U u)
  • ToLongBiFunction<T, U>
    抽象メソッドは int applyAsLong (T t, U u)
  • ToDoubleBiFunction<T, U>
    抽象メソッドは int applyAsDouble (T t, U u)

更に Function<T, R> には引数も返り値も指定されたプリミティブ型を返すものが用意されています。

こちらは BiFunction<T, U, R> 用は無いです。

  • IntToLongFunction
    抽象メソッドは long applyAsLong (int value)
  • IntToDoubleFunction
    抽象メソッドは double applyAsDouble (int value)
  • LongToIntFunction
    抽象メソッドは int applyAsInt (long value)
  • LongToDoubleFunction
    抽象メソッドは double applyAsDouble (long value)
  • DoubleToIntFunction
    抽象メソッドは int applyAsInt (double value)
  • DoubleToLongFunction
    抽象メソッドは long applyAsLong (double value)

Operator<T> は引数と返り値のプリミティブ型が一緒

Operator は引数と返り値が同じ型なので、Function<T, R> や BiFunction<T, U, R>  と違って名前に To がつきません。

  • IntUnaryOperator
    抽象メソッドは int applyAsInt (int value)
  • LongUnaryOperator
    抽象メソッドは long applyAsInt (long value)
  • DoubleUnaryOperator
    抽象メソッドは double applyAsInt (double value)
  • IntBinaryOperator
    抽象メソッドは int applyAsInt (int value1, int value2)
  • LongBinaryOperator
    抽象メソッドは long applyAsLong (long value1, long value2)
  • DoubleBinaryOperator
    抽象メソッドは double applyAsInt (double value1, double value2)

Consumer<T> だけ Object を引数で受け取る

Consumer<T> だけObject を引数で受け取ったインターフェースが存在します。

  • ObjIntConsumer<T>
    抽象メソッドは void accept (T t, int value)
  • ObjLongConsumer<T>
    抽象メソッドは void accept(T t, long value)
  • ObjDoubleConsumer<T>
    抽象メソッドは void accept(T t, long double)

まとめ

基本となる4つの関数型インターフェースとそのサブクラス、特殊化されたクラスについてまとめました。

関数型インターフェースには名前に Bi がついたり、To がついたり、メソッド名に As がついたりと規則性が見られます。

詳しい使い方やサンプルコードは少ないですが、java.util.function (Java Platform SE 8) 内の関数型インターフェースを似たようなグループでまとめたので、少しは見やすくなったかなと思います。

【Ruby on Rails】オプション付きのコマンドライン引数を扱う

概要

Ruby 製のスクリプトをサーバの /usr/local/bin などに配置して、自作コマンドとして使おうと思った事があります。

その時、コマンドラインから引数を渡して、引数によって処理を変更したいと考えていました。

Ruby でコマンドライン引数を扱う場合、ARGV を使います。

ただし、出来る事ならオプション付きでコマンドライン引数を扱いたい考えていました。

$ command <servername> で実行するのではなく、-s や --servername オプションを用意して $ command -s <servername> 見たいに実行するイメージです。

サーバーにインストールしたコマンドは他の人も使うため、--help オプションも用意しておき Usage (使用法) が表示できたら便利だなと思いました。

これらを作るのは結構面倒くさそうだなと思い調べていたら、オプション付きのコマンドライン引数を簡単に実装出来る optparse と言うライブラリを見つけました!

しかも、--help オプションや --version オプションがデフォルトで用意されています。

今回、optparse を利用して Ruby 製の自作コマンドを作った時のことをまとめます。

 

自作コマンド

ソースコードを見せた方が早いと思ったので hello と表示するだけの自作コマンドを用意しました。

ショートオプションとロングオプション両方を用意しています。

require 'optparse'

option = {}
OptionParser.new do |opt|
  Version = '1.0.0'
  # ショートオプション、ロングオプション、オプションの説明
  opt.on('-u', '--uppercase', 'Print hello in uppercase.') { |v| option[:u] = v }
# -m, --message ではオプションの後に値 <MESSAGE> が必須 opt.on('-m', '--message <MESSAGE>', 'Append message after hello.') { |v| option[:m] = v } opt.on('-v', '--version', 'Print version and exit.') { puts "Version #{opt.version}"; exit } opt.on('-h', '--help', 'Print usage and this help message and exit.') { puts opt; exit } begin
# オプション解析してくれる opt.parse! # 存在しないオプションを指定された場合 rescue OptionParser::InvalidOption => e puts "hello: #{e.message}" puts opt exit # オプション値が空だった場合 (--message の場合のみ) rescue OptionParser::MissingArgument => e puts "hello: #{e.message}" puts opt exit end end output = 'hello' output << " #{option[:m]}" if option[:m] output.upcase! if option[:u] puts output

--help (-h) オプション

--help を付けると以下のように表示されます。

--version と --help はデフォルトで用意されているので opt.on( ) に記載する必要はありません。

しかし、opt.on( ) に記載してないオプションは、 --help を付けた時に Usage (使用法) に表示されないので、わざわざ記載しています。

$ ./hello.rb -h
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

--version (-v) オプション

何も指定していないと version unknown になってしまいます。

折角なので Version = '1.0.0' としています。

これを設定すると opt.version で、コマンドの Version が取得出来るようになります。

# 何も指定していない場合
$ ./hello.rb -v
hello: version unknown

$ ./hello.rb -v
Version 1.0.0

--message (-m) オプション

--message の後ろに <MESSAGE> と書いていますが、これは --message を使う時にはオプション値が必要という意味になります。

--meessage の後ろに値が設定されていない場合は mission argument エラーとなります。

# -m オプションの後に値がない場合は missing argument エラーとなる
$ ./hello.rb -m
hello: missing argument: -m
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

# -m オプションの後に値を付けて実行する
$ ./hello.rb -m 'world!'
hello world!

--uppercase (-u) オプション

--uppercase を付けると大文字表示になります。

--message と併用もできます。(これはコマンドの作り方にもよります)

ただし、ショートオプションで -m と -u を合わせて -mu みたいに繋げて使うことは出来ないようです。

$ ./hello.rb -m 'world!' -u
HELLO WORLD!

# ショートオプションを繋げて使おうとすると想定外の結果となる $ ./hello.rb -mu 'world!' hello u

 

例外処理

基本的に存在しないオプションを指定された場合と、--message のようにオプション値が空だった場合の2パターンを抑えておけは充分だと思います。

例外発生時にはエラーメッセージと puts opt で Usage (使用法) を表示するようにしています。

# 存在しないオプションを指定された場合
$ ./hello.rb -a
hello: invalid option: -a
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

# オプション値が空だった場合    
$ ./hello.rb -m
hello: missing argument: -m
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

 

詰まったポイント

オプション値が必要な場合は明示的に指定する

# オプション値 <MESSAGE> あり
opt.on('-m', '--message <MESSAGE>', 'Append message after hello.') { |v| option[:m] = v }

# オプション値 <MESSAGE> なし
opt.on('-m', '--message', 'Append message after hello.') { |v| option[:m] = v }

と書いた場合、$ ./hello.rb -m 'world!' を実行して option[:m] に格納される値は world! ではなく true となります。

--message <MESSAGE> のように、明示的にオプション値が必須だと宣言しない限り、そのオプションが存在するかどうかの true/false が返り値となるようです。

optparse のバグ?

多分、前方一致になっているようで、ロングオプションを全部記述しなくても動いてしまいます。

# --message を --mes と短縮
$ ./hello.rb --mes 'world!'
hello world!

# --version を --ver と短縮
$ ./hello.rb --ver
Version 1.0.0

 

コマンド化する

今までローカルの hello.rb スクリプトを実行してたので /usr/local/bin に移動します。

# シンボリックリンクを貼る (絶対パスで指定する)
$ sudo ln -s /Users/<個人アカウント>/hello.rb /usr/local/bin/hello # Mac の場合
# もし間違えた場合
# $ unlink /usr/local/bin/hello

# 実行権限を付与する
$ chmod +x /usr/local/bin/hello

# 確認
$ which hello
/usr/local/bin/hello

# 実行
$ hello -m 'world!'
hello world!

 

まとめ

optparse ライブラリを使って Ruby 製の自作コマンドを作ってみました。

Ruby では ARGV を使えばコマンドライン引数を扱えるのですが、引数が 2 個や 3 個と増えた場合に、だんだん面倒になると思います。

何故なら 2 番目の引数がなかった場合のことや、2 番目と 3 番目が逆に設定された場合のことなどを考えないといけないからです。

オプション付きのコマンドライン引数であればそれらを考える手間が省けます。

--help オプションなど便利な機能がデフォルトで用意されていますし、Ruby 製の自作コマンドを作成する際には是非 optparse を使って見てください。

 

【AWS】WordPress でサイト構築時に phpMyAdmin をブラウザから見れるようにする

概要

AWS で WordPress を使えないかと思って調べてたところ、丁度良いのがあったので試してみました。

サイトの構築は以下の手順通りでとても簡単に出来ました。

aws.amazon.com

しかし WordPress と言ったら、一緒に phpMyAdmin も使うものだと思っていたら、phpMyAdmin にアクセス出来ませんでした。

その時調べたことをまとめました。

 

対応内容

phpMyAdmin をブラウザから見れるようにする

http://<パブリックIP>/phpmyadmin/index.php にアクセスると以下のようなエラーが表示されてブラウザからアクセスすることが出来ません。

For security reasons, this URL is only accessible using localhost (127.0.0.1) as the hostname.

これは Apache でローカルのアクセスのみ許可する設定を入れているのが原因です。

以下のサイトを参考に httpd.conf を書き換えて下さい。

kakistamp.hatenadiary.jp

ex1.m-yabe.com

$ vim /opt/bitnami/apps/phpmyadmin/conf/httpd-app.conf
# Apache 22 の設定 <IfVersion < 2.3 > Order allow,deny # Allow from 127.0.0.1 Allow from all # 追加 Satisfy all </IfVersion> # Apache 24 の設定 <IfVersion >= 2.3> # Require local Require all granted # 追加 </IfVersion>

書き換えが完了するとブラウザ経由で phpMyAdmin のログインページにアクセスすることができます。

f:id:kyamanak83:20181021193613p:plain

ここのユーザ名とパスワードは実際に WordPress でアクセスする DB のユーザ名とパスワードを入力する必要があります。

DB のパスワードは /home/bitnami/apps/wordpress/htdocs/wp-config.php に記載されています。

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'bitnami_wordpress');

/** MySQL database username */
define('DB_USER', 'bn_wordpress');

/** MySQL database password */
define('DB_PASSWORD', 'xxxxxxx');

/** MySQL hostname */
define('DB_HOST', 'localhost:3306');

/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');

/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');

DB アカウントのパスワードを変更する

既存のパスワードだといざという時に出てこないので、自分の好きなパスワードに変更します。

$ mysql -u bn_wordpress -p bitnami_wordpress
Enter password: # パスワード入力

# ログイン中のユーザーのパスワードを変更する場合 mysql> SET PASSWORD = PASSWORD('xxxxxxxxx'); Query OK, 0 rows affected, 1 warning (0.01 sec)

これで新しいパスワードを利用して phpMyAdmin にログインすることが出来るようになりました。

f:id:kyamanak83:20181021195258p:plain

 

しかし、このままだと WordPress 側で DB に接続している部分のパスワードが間違ったままなでアクセス出来ないので、先ほどの /home/bitnami/apps/wordpress/htdocs/wp-config.php を修正して下さい。

f:id:kyamanak83:20181021195537p:plain

 

 最後に

phpMyAdmin はブラウザ上で DB を好き勝って扱える便利だけど危険なツールなので AWS 側では非推奨にしているんだと思います。

もし、ブラウザからアクセスして利用するのであれば最低限以下の対応ぐらいはしておいた方が良いかもしれませんね。

def-4.com

【SSH】最近触らなくなった?SSH の設定ファイルを見直してみる

概要

最近ではレンタルサーバを借りても自分で SSH 周りの設定をすることはありません。

今回、たまたまセキュリティがざるなサーバ達を扱う機会があったので、SSH 周りで直しておいた方がいい設定をまとめました。

 

やる事リスト

他のサーバから SSH 接続される時の設定ファイル ( /etc/ssh/sshd_config ) を修正します。

  • SSH の認証方式を公開鍵認証に切り替える
  • SSH のポート番号を 22 番から変更する
  • root ユーザーの SSH ログインを禁止する
  • SSH のプロトコルを Version 2 に制限する

 

はじめに

公開鍵認証で SSH するためのキーペア(公開鍵と秘密鍵)をローカル PC で作成します。

既存のものがある場合には上書きしないように注意して下さい。

$ ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/kyamanak/.ssh/id_rsa): # Enter 押す
Enter passphrase (empty for no passphrase): # パスワード入力
Enter same passphrase again: # パスワード再入力

公開鍵は後ほどサーバにアップロードします。

 

対応手順

1. サーバーログイン

この時の SSH 接続は最後まで絶対に切らないで下さい!

設定を間違えると新規で SSH 接続出来なくなるので、その時に設定をロールバックするためです。

出来るだけみんなでログインした方がいいと思います。

2. 公開鍵をサーバーに登録

何かしらかの方法でサーバにアップロードしてください。最悪コピペでもいいです。

# 追加上書きをします
$ cat id_rsa.pub >> ~/.ssh/authorized_keys
# 権限は 600 が正しいです
$ chmod 600 ~/.ssh/authorized_keys

3. /etc/ssh/sshd_config の修正

参考:sshd の設定(sshd_config)

設定をミスった時のためにバックアップを用意しておきます。

$ cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bk
3.1. 認証方式を変更

公開鍵認証以外の認証方式を禁止します。

# パスワード認証禁止
PasswordAuthentication no
# チャレンジレスポンス認証禁止
ChallengeResponseAuthentication no
# Rhosts 認証禁止
RhostsRSAAuthentication no
IgnoreRhosts yes

パスワード無しユーザーのログインも禁止します。

PermitEmptyPasswords no

公開鍵認証を許可します。

RSAAuthentication yes
PubkeyAuthentication yes
3.2. 認証時の閾値変更

ついでにユーザがログインに成功するまでの制限時間を設定します。

デフォルトは 120 秒ですが、公開鍵認証であれば一瞬だと思うので 20 秒に変更しました。

ログインに失敗した時のリトライ回数も公開鍵認証であれば失敗しないと思うのデフォルトの 6 回から 3 回に変更します。

LoginGraceTime 20
MaxAuthTries 3
3.3. ポート番号を変更

SSH で利用しているポート番号は 22 番で、そのことは誰でも知っています。

そのため 22 番ポートは比較的狙われやすいポート番号となっています。

Port 22222

0 ~ 1023番までのウェルノウン・ポート以外で使っていないポート番号を指定してください。

今回は適当に 22222 番としました。

3.4. root ユーザーのログインを禁止

root ユーザーもほとんどのサーバで存在することがわかっているため標的とされやすいです。

root ユーザーを乗っ取られてしまうと何でも出来てしまうので、まず root ユーザーでログインを出来ないようにして乗っ取りのリスクを下げます。

PermitRootLogin no
3.5. プロトコルを Version 2 に制限

SSH のプロトコル Version1 には複数の脆弱性が見つかっている様なので、Version 2 を使う様に制限します。

Protocol 2

5. 再起動して設定反映

# 構文チェック
$ sshd -t
Could not load host key: /etc/ssh/ssh_host_rsa_key
Could not load host key: /etc/ssh/ssh_host_dsa_key
Could not load host key: /etc/ssh/ssh_host_ecdsa_key
Could not load host key: /etc/ssh/ssh_host_ed25519_key
# 再起動
$ service sshd restart
# 22222 番ポートが LISTEN になっている事を確認
$ sudo lsof -i:22222
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 13492 root 3u IPv4 40777 0t0 TCP *:22222 (LISTEN)
sshd 13492 root 4u IPv6 40786 0t0 TCP *:22222 (LISTEN)

6. 各自 .ssh/config を追加

これはローカル PC 側の作業です。

今までの設定を SSH 接続する際にオプションで毎回指定するのは面倒だと思うので .ssh/config に設定を記載します。

$ cat .ssh/config
Host <エイリアス>
    HostName <サーバー名 or IP アドレス>
    User <ユーザー名>
    Port 22222
    Protocol 2
    IdentityFile ~/.ssh/id_rsa

7. 新規 SSH 接続で動作確認

再起動したら他のターミナルから新規で SSH 接続が出来るか確認して下さい。

SSH 接続できなければ設定をロールバックする必要があります。

ここまで確認して問題なければ、初めて最初の SSH 接続を切断していいです。

 

最後に

最後に自分が使っている AWS の Ubuntu のサーバの /etc/ssh/sshd_config と比べて見ます。

AWS のサーバはデフォルトで今回の設定がほとんど入っていました。

多くの利用者がいるレンタルサーバの /etc/ssh/sshd_config を真似するのも一つの手かもしれませんね。

# Package generated configuration file
# See the sshd_config(5) manpage for details

# What ports, IPs and protocols we listen for
Port 22222
# Use these options to restrict which interfaces/protocols sshd will bind to
#ListenAddress ::
#ListenAddress 0.0.0.0
Protocol 2
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
#Privilege Separation is turned on for security
UsePrivilegeSeparation yes

# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 1024

# Logging
SyslogFacility AUTH
LogLevel INFO

# Authentication:
LoginGraceTime 20
MaxAuthTries 3
PermitRootLogin no
StrictModes yes

RSAAuthentication yes
PubkeyAuthentication yes
#AuthorizedKeysFile	%h/.ssh/authorized_keys

# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes

# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no

# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no

# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes

# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes

X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
#UseLogin no

#MaxStartups 10:30:60
#Banner /etc/issue.net

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

Subsystem sftp /usr/lib/openssh/sftp-server

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes

ClientAliveInterval 180

Ciphers aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se

【Ruby on Rails】POST リクエストにリトライ処理を入れる

概要

HTTP リクエストは必ずしも成功するものではありません。

リクエストを受け取るサーバ側の問題だったり、ネットワークの問題だったりで稀に失敗する事があります。

そう言った場合にリトライ処理(再送処理)を入れて、ユーザーからのリクエストを失敗させないようにします。(リトライ処理を入れる事で失敗する確率を下げます)

今回は HTTP リクエストの POST で再送処理を行う方法をまとめました。

 

はじめに

今回のリトライ処理はあくまで相手側のサーバまでリクエストが到達しなかった場合に再送する事を想定しています。

相手側のサーバまでリクエストが到達したものの、レスポンスが 200 Success 以外だったため再送すると言ったものではありません。

例えば、リクエストパラメータが不足した状態でリクエストすると 400 Bad Request が返ってきます。

リクエストパラメータが不足した状態でリトライし続けても成功することはありません。

また、相手側のサーバで何か問題が発生している場合 500 Internal Server Error が返ってきます。

同じくこの状態でも、リトライを繰り返しても成功する確率は低いと思います。

この場合はリトライ処理で何度も試すのではなく、相手側のサーバが復旧するまで待つのが得策かと思われます。

 

対応方針

ruby の例外処理には retry 機能が用意されているので、それを利用します。

retry は、rescue 節で begin 式をはじめからもう一度実行するのに使用します。 retry を使うことである処理が成功するまで処理を繰り返すようなループを作 ることができます。

begin
  do_something # exception raised
rescue
  # handles error
  retry  # restart from beginning
end

参考:制御構造 (Ruby 2.5.0)

 

サンプルコード

今回、用意したソースコードがこちらです。

  • リクエスト処理は、HTTPS のサイトに JSON 形式で POST する事を想定して作っています
  • リトライ処理は、リクエスト処理で例外が発生した場合、5 秒間隔で 3 回繰り返す様に作っています

ENDPOINT と body に好きな値を入れればコピペして使えるかもしれません。

今回は POST 用のサンプルコードですが、GET や DELETE、PUT/PATCH でも少し書き換えれば使えると思います。

HTTPS へのリクエストですが証明書の検証は無し OpenSSL::SSL::VERIFY_NONE を指定しているので、必要があれば書き換えてください。

以下の値も適宜書き換えて利用してください。

  • RETRY_MAX_COUNT:リトライ処理の回数
  • RETRY_WAIT_TIME:リトライ処理の間隔(待ち時間)
  • REQUEST_TIMEOUT:リクエスト時のタイムアウト秒

 

require 'net/https'
require 'openssl'
require 'json'

ENDPOINT = '' # お好きなリクエスト先を指定してください
RETRY_MAX_COUNT = 3 # 回
RETRY_WAIT_TIME = 5 # 秒
REQUEST_TIMEOUT = 5 # 秒

def post(body)
  uri = URI.parse(ENDPOINT)
  # JSON 形式でリクエストを送信する場合の MIME タイプ
  header = { 'Content-Type' => 'application/json' }
  req = Net::HTTP::Post.new(uri.request_uri, header)
  req.body = body.to_json # JSON 形式でリクエストを送信
  http = Net::HTTP.new(uri.host, uri.port)
  http.open_timeout = REQUEST_TIMEOUT
  http.use_ssl = true # https 通信
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE # 今回はテスト用なので証明書の検証なし
  http.start { http.request(req) }
end

def post_with_retry(body)
  retry_count = 0
  begin
    post(body) # POST メソッド呼び出し
# raise 'POST できません' # 動作確認用(意図的に Exception を発生させる) rescue => e if retry_count < RETRY_MAX_COUNT sleep RETRY_WAIT_TIME puts "retry count: #{retry_count += 1}" # retry count を表示しながらカウントアップ retry else puts e.message end end end body = '' # 相手側の仕様に従って記述してください
# 例) body = { message: 'hogehoge' } # post メソッド内で to_json しているのでここはシンボル形式で OK です response = post_with_retry(body) puts response.code # レスポンスコードを表示

 

まとめ

 ruby の例外処理で用意されている retry を利用する事で簡単に POST リクエストの再送処理を行うことができます。

GET リクエストは取得できなかった場合分かりやすいですし、ユーザーもブラウザのリロードなどで勝手に(リトライ処理的な)対応をしてくれる事が多いです。

POST リクエストが失敗すると、ユーザーに初めから入力フォームに入力し直す手間をかけたり、悪い印象も与えます。

ネットワークの瞬断やサーバの障害は起こるものなのでリトライ処理は POST に限らず記載しておくといいと思います。

コピペ用のソースコード良かったら使ってくださいー!

【Ruby on Rails】rubocop と pre-commit を利用して git commit 時にコーディングチェックを行う

概要

Rails アプリケーションに rubocop と pre-commit という gem をインストールして、git commit 時にコーディングチェックを行うようにします。

もし、コーディング規約に準拠しないコードがあった場合はコミットが中断されます。

 

rubocop  とは

rubocop とは Ruby で書かれたソースコードを解析して、コーディング規約に準拠していない部分を教えてくれるツールです。

Ruby 初心者でも rubocop を利用すれば綺麗なコードに早変わりします。

github.com

使い方

まずは rubocop をインストールします。(又は Gimfile に記載してください)

$ gem install rubocop

rubocop コマンドの引数にコーディングチェックしたい .rb ファイルを指定して実行します。

すると、以下の様にコーディング規約に違反している部分を教えてくれます。

$ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

今回は3箇所コーディング規約に違反している部分が見つかりました。

 

実は rubocop には便利なオプションがあり、-a オプションを付けると、コーディング規約に違反したコードを自動で修正してくれます。

# 修正前
$ cat test.rb
array = ['aaa', 'bbb', 'ccc']

array.each { |i|
  if i =~ /^a/
    puts i
  end
}
# -a オプションで自動修正
$ rubocop -a test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: [Corrected] Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: [Corrected] Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: [Corrected] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected, 3 offenses corrected
# 修正後
$ cat test.rb
array = %w[aaa bbb ccc]

array.each do |i|
  puts i if i =~ /^a/
end

rubocop さんがソースコードを良い感じに直してくれたのが分かります。

注意点

しかし rubocop のコーディング規約には少々使いにくい部分もあります。

例えは、日本語のコメントを書いたり、一行に 80 文字以上書いたりすると注意を受けます。

日本人なので日本語のコメントの方が分かりやすいと思いますし、一行にかけるコードも 80 文字制限は少々厳し気がします。

そういう場合は .rubocop.yml を用意します。

このファイルを用意すると、rubocop コマンドを実行した場合に .rubocop.yml に書かれたルールがデフォルトのルールを上書きする形で適応されるます。

ルールが適応されるのは、.rubocop.yml が置かれたディレクトリよりも下の階層になります。

$ vim .rubocop.yml
# 日本語のコメントを OK とする
Style/AsciiComments:
  Enabled: false

# 一行に 120 文字まで書いても OK とする Metrics/LineLength: Max: 120

コーディング規約とは自分たちが開発するプロジェクトにおいて、誰が開発してもプログラミング品質が均一になるために守るべきルールです。

デフォルトのルールに従えば世界共通の認識になるかもしれませんが、ある程度は自分たちのプロジェクトが開発しやすいようにカスタマイズしても問題ないと思います。

 

pre-commit とは

pre-commit とはコミット時に実行して欲しい処理を記載したものです。

実行後の終了ステータスが 0 (成功) でないと、コミットが中断するようになっています。

参考:Git - Git フック

github.com

使い方

まずは pre-commit をインストールします。(又は Gemfile に記載してください)

$ gem install pre-commit

以下のコマンドを実行すると対象レポジトリに .git/hooks/pre-commit というファイルが作成されます。

$ pre-commit install
Installed /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/pre-commit-0.38.1/templates/hooks/automatic to .git/hooks/pre-commit

# ファイルを確認
$ ls -la .git/hooks/
total 56
drwxrwxr-x 2 ec2-user ec2-user 4096 Jun 19 20:42 .
drwxrwxr-x 7 ec2-user ec2-user 4096 Jun 19 20:43 ..
-rwxrwxr-x 1 ec2-user ec2-user  478 Jun 19 20:40 applypatch-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  896 Jun 19 20:40 commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  189 Jun 19 20:40 post-update.sample
-rwxrwxr-x 1 ec2-user ec2-user  424 Jun 19 20:40 pre-applypatch.sample
-rwxr-xr-x 1 ec2-user ec2-user 1104 Jun 19 20:42 pre-commit # 新しく作成された
-rwxrwxr-x 1 ec2-user ec2-user 1642 Jun 19 20:40 pre-commit.sample
-rwxrwxr-x 1 ec2-user ec2-user 1348 Jun 19 20:40 pre-push.sample
-rwxrwxr-x 1 ec2-user ec2-user 4898 Jun 19 20:40 pre-rebase.sample
-rwxrwxr-x 1 ec2-user ec2-user  544 Jun 19 20:40 pre-receive.sample
-rwxrwxr-x 1 ec2-user ec2-user 1239 Jun 19 20:40 prepare-commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user 3610 Jun 19 20:40 update.sample

 

しかし、これだけではまだ git commit 時に rubocop が呼び出される様な設定になっていません。

$ pre-commit list
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : common rails # 何も設定されていない
Enabled   checks   : common rails # 何も設定されていない
Evaluated checks   : tabs nb_space whitespace merge_conflict debugger pry local jshint console_log migration
Default   warnings :
Enabled   warnings :
Evaluated warnings :

# 以下のコマンドで git commit 時に rubocop が呼ばれる様にします
$ git config pre-commit.checks rubocop

$ pre-commit list
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : rubocop # rubocop が実行される様になった
Enabled   checks   : rubocop # rubocop が実行される様になった
Evaluated checks   : rubocop # rubocop が実行される様になった
Default   warnings :
Enabled   warnings :
Evaluated warnings :

 

実際に設定が反映されているかコミットを試して見ます。

$ git commit
pre-commit: Stopping commit because of errors.
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

pre-commit: You can bypass this check using `git commit -n`

すると、先ほどの rubocop のエラーが表示され、commit が中断された事が分かります。

Stopping commit because of errors. と書いてありますね。

設定を解除したい場合

設定を解除したい場合、.git/hooks/pre-commit ファイルを削除するかリネームしてください。

$ rm .git/hooks/pre-commit # 削除
# 又は
$ mv .git/hooks/pre-commit .git/hooks/pre-commit.ruby.sample # リネーム

 

まとめ

rubocop と pre-commit の gem を導入して、git commit 時にコーディングチェックを行うようにしました。

自分は開発環境で複数のアプリケーションを開発したり、一次的に git clone して修正したりしたい人なので、Gemfile に記載して特定の Rails アプリケーションだけ rubocop と pre-commit を導入せずに gem install でサーバに導入してしまいました。

最低でも rubocop は導入して自分の書いたコードをリファクタリングする癖をつけると良いと思います。

忘れっぽい人は pre-commit も導入して強制化するのも手だと思います。

 

【Ruby on Rails】オートコンプリート機能付き検索フォームを実装する

概要

Ruby on Rails でオートコンプリート機能付きの検索フォームを実装してみます。

 

gem の紹介

今回はこちらの gem を利用します。

rails4-autocomplete という名前ですが、rails5 でも利用できます。

github.com

 

対応手順

1. Gemfile の修正

初めに Gemfile に rails4-autocomplete を追加します。

Gemfile に記載したら bundle install でパッケージをインストールします。

$ vim Gemfile
gem 'rails4-autocomplete' # 追記

# Gemfile に書かれた gem パッケージと、その依存パッケージをインストールします
$ bundle install --path vendor/bundle

2. application.js の修正

次に app/assets/javascripts/application.js に autocomplete-rails.js を利用する旨の記述をします。

これで rails4-autocomplete に含まれている  autocomplete-rails.js を利用できるようになります。

//= require jquery
//= require jquery_ujs
//= require autocomplete-rails # 追加
//= require turbolinks
//= require_tree .

3. Controller の修正

ここまで完了したら、オートコンプリート機能を利用したいクラスの Controller に以下のアクションを追加します。

引数には取得したい model 名と column 名をセットで記載します。

他のクラス又は全てのクラスで利用できるように application_controller.rb に書いてしまうのも良いと思います。

今回、自分はそうしてます。

class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception
# 第 1 引数 => model名 :user (必須)
# 第 2 引数 => column名 :name (必須)
# 第 3 引数 => オプション full: true (任意) autocomplete :user, :name, full: true # 追加 end
full オプション

デフォルトは前方一致検索のみです。全検索(部分一致検索・後方一致検索)したい場合には設定を true にします。

例えば、ruby on rails と言うワードを検索したい場合、デフォルトでは ruby で検索した場合しかヒットしません。

このオプションを true にすると on や rails で検索した場合もヒットするようになります。

他にもオプションがあるので確認してください。

https://github.com/peterwillcn/rails4-autocomplete#options

4. ルーティングの設定

先ほど作成したアクション名を使って、ルーティングの設定を追加します。

$ vim config/routes.rb

Rails.application.routes.draw do
  resources :users do
    get :autocomplete_user_name, on: :collection # 追加
  end
end

 

これで /users/autocomplete_user_name と言うパス (URL) が作成されます。

curl コマンドを叩いて動作確認をしてみます。

すると以下のようにユーザー名一覧の配列が取得できます。

※ この時点で既に users テーブルにデータを格納しておく必要があります。

オートコンプリート機能は Ajax 処理で、検索フォームに何かワードを入れるとバックエンドでこの URL を叩いて情報を取得してフロントエンドに表示しています。

$ curl 'http://localhost/users/autocomplete_user_name'

["aaaaaaaaaa","bbbbbbbbbb","cccccccccc","dddddddddd","aaaaabbbbb","cccccddddd","aaaaaccccc","bbbbbddddd"]               

5. View の修正

最後にオートコンプリート機能付きの検索フォームを表示したい箇所に以下の View ヘルパーを設置します。

<%= autocomplete_field_tag 'user[name]', nil,  autocomplete_user_name_users_path %>

 

検索フォームが表示されると思いますので、何かワードを入力してオートコンプリート機能が備わっているか確認してください。

f:id:kyamanak83:20180527221847p:plain

ただし、この時点ではただの検索ボックスみたいなもので Controller 側に入力されたデータを渡す機能がありません。

6. form の実装

検索フォームで入力された値を Controller に渡せるようにします。

今回は users_path('/users')に入力データを渡します。

Rails は URL にアクセスする HTTPメソッドによって挙動が変わります。

'/users' に POST でデータを渡すと新規作成になってしまうので、GET で渡すようにしてください。

また Rails の form_tag はデフォルトではパラメーターに utf8=✓ が付与されます。

こちらも不要だと思ったので enforce_utf8: false で除外しました。

<%= form_tag(users_path, method: :get, enforce_utf8: false) do %>
  <%= autocomplete_field_tag 'user[name]', nil,  autocomplete_user_name_users_path %>
<% end %>

7. 検索機能の実装

検索フォームで入力されたデータを Controller で受け取ります。

params[:user][:name] に入力されたデータが入っています。

Controller の処理は以下のようにしました。

  • 検索クエリがあれば該当するユーザーを返します。
  • 検索クエリがなければ全ユーザーを返します。

 

# GET /users
# GET /users.json
def index
# 検索クエリ: params[:user][:name] if params[:user] && params[:user][:name] user_name = params[:user][:name] @users = User.where("name LIKE '%#{user_name}%'") else @users = User.all end end

オートコンプリート機能が付いていれば、検索フォームから入力されたデータは補完された上で Controller 側に渡されるので、完全一致でも良いかもしれません。

※ User.where(name: user_name) でも良いと言う事です。

 

まとめ

オートコンプリート機能の付きの検索フォームを実装しました。

gem を利用すればフロントエンド側は autocomplete_field_tag を記載するだけで簡単に実装する事ができます。

バックエンド側は検索クエリに一致するデータを DB から取得してフロントエンド側に返す処理が必要です。

検索ワードがある程度限定された検索フォームを作成する場合は、是非導入してみてください。

【Ruby on Rails】Mysql2::Error: Data too long for column 'xxxxx' at row 1:

概要

自分が担当しているシステムでユーザーからデータの保存ができないとお問い合わせを受けたことがあります。

システムが高負荷になっていた訳でもなく、そのユーザー以外は普通に利用できていたので、最初は原因が分からなかったのですが、ログを調べて見たらあるエラー吐かれていました。

今回、そのエラーについて調べたときの事をまとめました。

 

エラー文言

以下のエラーが発生して DB へのデータ挿入に失敗していました。

ActiveRecord::ValueTooLong(Mysql2::Error: Data too long for column 'xxxxx' at row 1: INSERT INTO 'table' ('column1', 'column2', 'column3'....))

 

原因

対象カラムのデータ型が varchar (255) となっており、ユーザーが挿入しようとしたデータが 255 バイト以上だったため、カラムに格納できる最大バイト数を超えてエラーとなっていました。

 

解決策

解決策としては 2 つの方法を考えました。

  • フロントエンド側で入力フォームの文字数制限を入れる
  • 対象カラムのデータ型を変更する

最終的にはどちらも実装した方が良いのですが、今回はユーザーがデータを保存できないエラーを解決したかったので、取り急ぎ対象カラムのデータ型を変更する方法をとりました。

余力があれば、フロントエンド側にテキストボックスの文字数制限を入れようと思います。

また今回、例外処理が入っていなかったので、例外処理の記述も追加したいと思います。

何のデータ型にすれば良いのか?

そもそも varchar (255) にした理由は rails generate scaffold でカラムを生成した際に、string 型で作ろうとしたらデフォルトで設定されたデータ型だったからです。

参考:【Rails・MySQL】MySQLのデータ型とRailsのマイグレーションファイルのデータ定義の対応まとめ

 

まず初めに変更を考えたデータ型は text 型です。

text 型で格納できるバイト数は 2 の 16 乗なので 65536 バイトとなります。

参考:MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.7 データ型のストレージ要件

 

しかし、今まで最大 255 バイトで十分だったカラムにいきなり 65536  バイトも設定する必要があるでしょうか?

そこで varchar の最大バイト数を変更する方法を取りました。

具体的には varchar (255) を varchar (500) としました。

mysql> alter table 'table名' modify 'column名' varchar(500); 

データ型変更は既存のデータに影響を与える処理なので、本来であれば慎重に行わないといけません。

しかし、今回の変更は最大長を 255 バイトから 500 バイトに変更しただけなので問題が発生する可能性は極めて少ないと思われます。

日本語文字って何バイトか?

ちなみに日本語って何バイトなんだろうと疑問に思いました。

文字コードによって違うようで、全角 1 文字 2 バイトなのは EUC-JP や Shift_JIS を使ったときのようです。

現在主流の UTF-8 を使うと 2 ~ 4 バイトで表されるようです。

参考:マルチバイト文字を扱う際に気をつけること

例外処理を入れる

最大長を 500 バイトに更新しましたが、それ以上のデータを挿入された場合の為に例外処理を書いておきます。

begin
# ** DB にデータを挿入する処理 **
# 例えば
# # @user.create
# # @user.save
# # など rescue ActiveRecord::ValueTooLong => e  # ** 例外が発生した場合の処理 **
# 例えば
# # エラーページにリダイレクト
# # return redirect_to '/500.html'
# # エラーログを出力する
# # log.error(e.message)
# # インスタンス変数を使って View 側にメッセージを表示
# # @is_toolong_value = true
# # など end

 

まとめ

rails generate scaffold で strign 型のカラムを作成するとデフォルトでは varchar (255) となります。

ユーザー入力では開発者が想定外のものを入力されることが稀にありますので、フロントエンド側でしっかり制御したり、格納できるデータは余裕をもって大きい値にしておく事をお勧めします。

【Apache】502 Bad Gateway proxy: error reading status line from remote server

概要

内部ネットワークからインターネットへのアクセスをプロキシしているサーバで稀に 502 Bad Gateway のエラーを返していることがありました。

リクエスト元のサーバではリトライ処理を入れていたので問題は無かったのですが、通常時は 200 で成功しているリクエストが、突然 502 で失敗してしまうのは謎でした。

今回、その時に調査したことをまとめました。

 

エラー文言

リモートサーバのステータスラインの読み込みに失敗しました。

proxy: error reading status line from remote server...

ステータスラインとは HTTP/1.1 200 OK のことです。

HTTP の Version と HTTPステータスコードとメッセージが一行に記載されています。

おそらくリモートサーバにプロキシ出来ていないんだと思います。

 

原因

これは Apache 2.1 以降の機能で ProxyPass を利用する際に、コネクションプールを使うようになったのが原因です。

コネクションプールとは、リクエストの度にコネクションを生成するのではなく、コネクションを事前に用意しておいて再利用しようと言う考え方(仕組み)のことです。

コネクションを再利用することで、コネクションを生成する時にかかる(接続に必要とされる)オーバーヘットをカットして双方の負荷を軽減してくれます。

このエラーは事前に用意されたコネクションを使って接続しようとしたら、接続中にコネクションを切られてしまい接続できなかった時に発生するエラーだと思われます。

参考:mod_proxy - Apache HTTP サーバ バージョン 2.4

 

解決策

コネクションプールを使わない設定を追加します。

SetEnv proxy-initial-not-pooled 1

Apache の公式ドキュメントにも proxy: error reading status line from remote server のエラーが起きたらこれを設定してと書いてあります。

ただし、注意点としてコネクションプールを使わない場合、リクエストの度にコネクションを生成するので、パフォーマンスが劣化するらしいです。

参考:mod_proxy_http - Apache HTTP Server Version 2.4

 

パフォーマンス確認

パフォーマンスが劣化すると書いてあったので、proxy-initial-not-pooled の設定を追加したサーバでしばらく様子を見ました。

サーバの CPU 使用率やメモリ使用率は設定を追加した前後で変化はありませんでした。

Apache のレスポンスタイムも特に遅くなったりせず、設定を追加したことによって他のエラーが発生するなどの事象も起きませんでした。

おそらく、影響度はサーバの負荷(リクエスト数)によって違うんだと思います。

ちなみに、今回対応したサーバの秒間リクエスト数は 50 以下でした。

パフォーマンスが劣化した場合は、プロキシサーバを増設して負荷を分散できればパフォーマンスの劣化も防げるかも知れませんね。

 

【Apache】http 通信を https 通信に変換してプロキシする

概要

内部ネットワークのサーバがインターネット上のサーバにアクセスする場合、セキュリティ上の理由でプロキシサーバを経由してアクセスする場合が多いと思います。

参考:プロキシサーバーを使うと、なぜセキュリティが向上するのか?

tech.nikkeibp.co.jp

 

最近では Google が推奨していることもあってか、常時 SSL/TLS のサイトが増えています。

常時 SSL/TLS とは、個人情報を扱うページだけではなくサイト全体を SSL/TLS 化しましょうと言う取り組みです。

www.websecurity.symantec.com

そのため、インターネット上で公開している API のサイトなども https に切り替わってきています。

今回、今まで http でアクセスしていたサイトが https に切り替わるため急遽対応した時のことをまとめました。

本来であれば、リクエストしている箇所のソースを修正するのが正しいやり方だと思うのですが、時間がなく修正したパッケージをリリースするサーバ台数も多かったので、プロキシサーバの設定を更新する方法を取りました。

 

必要なパッケージ

Apache24 で SSL/TLS Proxy を利用するには mod24_ssl と言うパッケージが必要となります。

$ sudo yum install httpd24 mod24_ssl

mod24_ssl のパッケージがインストールされていない環境で https のサイトにプロキシしようとすると以下のエラーが発生します。

Invalid command 'SSLProxyEngine', perhaps misspelled or defined by a module not included in the server configuration

 

対応方法

SSL/TLS Proxy を実現するには、設定ファイルの中で SSLProxyEngine on を記載します。

参考:mod_ssl - Apache HTTP Server Version 2.4

$ sudo vim /etc/httpd/conf/httpd.conf
== 追加 ==
SSLProxyEngine on
ProxyPass /sha2 https://sha256.badssl.com/
ProxyPassReverse /sha2 https://sha256.badssl.com/

上記の httpd.conf の設定は /sha2 配下に来たアクセスを、sha256.badssl.com のサイトにプロキシします。 

 

もしも SSLProxyEngine on の設定なしで、SSL/TLS Proxy しようとすると以下のエラーが発生して Proxy には失敗します。

$ tail /etc/httpd/logs/error_log
[Sun Apr 29 11:00:41.577274 2018] [ssl:error] [pid 3512] [remote XXX.XXX.XX.XXX:443] AH01961: SSL Proxy requested for ip-XXX-XX-XX-XXX.ap-northeast-1.compute.internal:80 but not enabled [Hint: SSLProxyEngine] [Sun Apr 29 11:00:41.577315 2018] [proxy:error] [pid 3512] AH00961: HTTPS: failed to enable ssl support for XXX.XXX.XX.XXX:443 (sha256.badssl.com)

 

余談

SSLProxyEngine on が記載された設定ファイルに http の proxy と https の proxy が混ざり合っていても問題ありません。

SSLProxyEngine on
# https の proxy
ProxyPass /hoge https://hogehoge.com/
ProxyPassReverse /hoge https://hogehoge.com/
# http の proxy
ProxyPass /fuga http://fugafuga.com/
ProxyPassReverse /fuga http://fugafuga.com/

こんな記載方法でも問題なく Proxy できます。

 

HBase とは?HBase 初心者が最初に知っておいた方がいいと思った事まとめ

概要

新しく HBase を扱うサーバ担当になりました。

IT 業界ではデータ利活用の分野が活発になっており、ビッグデータを安全かつ高速に扱える HBase のようなプラットフォームは今後ますます注目されると思います。

しかし、自分は HBase について全く知識が無かったので、今回 HBase について調べて、重要だと思う点をまとめました。

 

HBase とは?

HBase とは Google のほとんどのサービス基盤を支えるで BigTable を元に設計されたオープンソースソフトウェア (OSS) です。

NoSQL の一つであり、Hadoop の分散ファイルシステムである HDFS 上で動作する列指向型の分散データベースです。 

低レイテンシーでデータの読み書きを行うことに優れており、強い一貫性を保証してくれます。

ネットワーク障害が発生した場合でも、通信可能なサーバだけでシステムを稼働し続けることができる耐性を持っています。(ただし処理性能が落ちる可能性あり)

 

構成

HBase は Master サーバと Region サーバの 2 種類のサーバで構成されています。

Region サーバを増設することで簡単にスケールアウト出来る仕組みとなっています。

HBase のデータはリージョンと呼ばれる単位で分割されており、リージョンの扱い方でサーバの種類が分けられます。

Master サーバ

HBase の全データ(リージョン)がどの Region サーバに保存されているかメタ情報を管理しているサーバです。

Region サーバへのリージョンの割り当てや、Region サーバの障害検知も行います。

ちなみにこれらの仕事は Master サーバ内で動いている ZooKeeper と言うソフトウェアが担当しています。

ZooKeeper は HBase 以外にも多くの分散アプリケーションで利用されているソフトウェアです。

Region サーバ

Master サーバから割り当てられたリージョンを保存しているサーバです。

クライアントはまず Master サーバ(正確には ZooKeeper)に問い合わせをして、対象のデータ(リージョン)を保存している Region サーバの位置を教えてもらいます。

その後、その Region サーバがクライアントからのリクエストを担当します。

リージョンは必ず単一の Region サーバで管理されており、読み書きを行うためデータの一貫性が保たれます。

Region サーバがダウンした場合は、割り当てられていたリージョンは他の Region サーバにフェールオーバーされます。(この作業は Master サーバが行う)

ただし、フェールオーバーが完了するまで、クライアントは対象リージョンへの読み書きを行うことができません。

 

データ構造

HBase のテーブルには型がなく、バイト配列(byte[ ])で格納されています。

HBase の行は一意な行キーで昇順にソートされており、テーブルの値を読み書きする際にはこの行キーを介して行います。

前述しましたが、HBase で管理するデータは一定範囲毎にリージョンと言う単位で分割されており、テーブルには複数のリージョンが存在します。

また、HBase の列も列ファミリと言う単位でグループ化されています。

テーブルのデータはリージョン毎に分割されて、更に列ファミリ毎に分割されてファイルに吐き出されます。

ファイルは別でも同じリージョンであれば、同じ Region サーバに保存されます。

f:id:kyamanak83:20180324162702p:plain

HBase のデータ(セルの値)にはそれぞれタイムスタンプが付与されており、バージョンを管理しています。

ファイルには以下のようなフォーマットで保存されます。

行(行キー):列ファミリ:列:タイムスタンプ:値

 

HBase の書き込み

HBase では書き込みを行う際に、まず初めに Region サーバの MemStore 上にデータを書き込みます。

MemStore とは Region サーバのメモリー領域であり、キャッシュのような働きをします。

そして MemStore のデータが一定量溜まったところでディスク上に HFile としてデータを吐き出し永続化します。

ただし、HFile に吐き出す前のデータはサーバのメモリ上 (MemStore) に格納されているため、サーバがダウンすると消えてしまいます。

そのため、HBase では HLog と言うデータを書き込む際のログを残しており、サーバがダウンした際には HLog を元にデータを復旧出来るようになっています。

 

まとめ

HBase 初心者の自分が、HBase を一通り調べて重要だと思う点をまとめました。

基本的なことしか書いていないので、これから開発・運用をしていく中で知らない事に沢山出会うと思っています。

HBase には独自の用語が多く、それを知っていないと話について行けないと思いました。

会社のメンバーはオライリーの HBase の本を読んで勉強しているので、一応紹介しておきます。

 

ちなみに、自分はまだ読んでませんw

【Ruby on Rails】whenever を使って定期的にバッチ処理を行う

概要

Ruby on Rails で定期的にバッチ処理を実行する方法をまとめます。

定期的に処理を実行したい場合、クーロンを利用するのが定石かと思います。

クーロンを実行するには、crontab に設定を記載する必要があります。

Ruby on Rails の場合、lib ディレクトリ内に置かれたモジュールや Rakefile に書かれたタスクを定期的に実行したいと思う事が多いと思います。

しかし、これらを処理を crontab に記載するのは慣れてないと少々難しいです。

何故なら、Rakefile のタスクだったり、モジュールの実行は、Rails アプリケーションの ROOT ディレクトリで実行する必要があるからです。

更に Rails の環境ごとに実行だったり、ログ出力だったりを考えると、どんどん複雑になってきます。

そこで、これらの設定を簡単に crontab に自動生成してくれる whenever と言う便利な gem があるので、それの使い方をまとめました。

 

導入方法

gem インストール

github.com

Gemfile に whenever を追記してインストールしてください。

$ vim Gemfile
gem 'whenever', require: false

# インストール
$ bundle install --path vendor/bundle

設定ファイル生成

Rails アプリケーションの ROOT ディレクトリで以下のコマンドを実行して下さい。

そうすると config ディレクトリ内に schedule.rb と言うファイルが生成されます。

この schedule.rb がクーロンの設定を記載するファイルです。

$ bundle exec wheneverize .
[add] writing './config/schedule.rb' # schedule.rb ファイルが生成
[done] wheneverized!

 

定期的に実行したい処理を記載

例として 2 つのバッチ処理を用意しました。

  • 毎週月曜日 AM 4:30 と PM 6:00 に Rakefile に記載された hoge タスクを実行する
  • 4 日おきに Hoge モジュールの hoge メソッドを実行する

crontab の書式と違って、schedule.rb の書式の方が書きやすいし、読みやすいと思います。

また、ruby ファイルなのでメソッドを使えたり、変数を使えたりするのが便利です。

$ vim config/schedule.rb
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron

require File.expand_path(File.dirname(__FILE__) + "/environment")

rails_env = Rails.env.to_sym
rails_root = Rails.root.to_s

# environment は設定しないと production になってしまう set :environment, rails_env set :output, "#{rails_root}/log/cron.log"
# 毎週月曜日 AM 4:30 と PM 6:00 に rake タスクを実行する every :monday, at: ['4:30 am', '6:00 pm'] do rake 'hoge' end # 4 日おきに Hoge モジュールの hoge メソッドを実行する every 4.days do runner 'Hoge.hoge' end

詳しい書き方は README.md を参考にして下さい。

whenever/README.md at master · javan/whenever · GitHub

 

設定の反映・削除など

設定ファイルが出来上がったら whenever のコマンドを実行して、crontab を生成します。

設定の確認
# 生成される crontab が問題ないか確認
$ bundle exec whenever
30 4 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 18 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 0 1,5,9,13,17,21,25,29 * * /bin/bash -l -c 'cd /var/workspace/rails_app && bundle exec bin/rails runner -e development '\''Hoge.hoge'\'' >> /var/workspace/rails_app/log/cron.log 2>&1'
設定の反映
$ bundle exec whenever --update-crontab
[write] crontab file updated

# crontab に設定が反映されたことを確認
$ crontab -l
# Begin Whenever generated tasks for: /var/workspace/rails_app/config/schedule.rb at: 2018-02-18 16:52:03 +0900
30 4 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 18 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 0 1,5,9,13,17,21,25,29 * * /bin/bash -l -c 'cd /var/workspace/rails_app && bundle exec bin/rails runner -e development '\''Hoge.hoge'\'' >> /var/workspace/rails_app/log/cron.log 2>&1'

# End Whenever generated tasks for: /var/workspace/rails_app/config/schedule.rb at: 2018-02-18 16:52:03 +0900
設定の削除
$ bundle exec whenever --clear-crontab
[write] crontab file

# crontab から設定が削除されたことを確認
$ crontab -l
# 何も出てこなければOK

 

生成された crontab を見ると以下の処理をワンライナーで実行してくれているのが分かります。

  1. Rails アプリケーションの ROOT ディレクトリに移動
  2. 環境変数をセット (rake の場合) or 環境を指定 (rails runner の場合)
  3. コマンドを実行
  4. 標準エラーを標準出力にマージしてログ出力

 

まとめ

whenever を利用する事で Rakefile に記載されたタスクや、lib ディレクトリ内で管理されたモジュールを簡単に呼び出して crontab に記載する事ができました。

複雑なワンライナーを crontab に自動生成してくれる点以外にも、以下のメリットがあると思います。

  • schedule.rb の方が書式が見やすい・書きやすい
  • ruby ファイルなので凝った事ができる
  • 設定ファイルを config ディレクトリ内で一元管理できる

「設定ファイルを config ディレクトリ内で一元管理できる」メリットとは、crontab.conf などを別途作って GitHub で管理する場合、何処のディレクトリに配置すればいいのか悩みます。

schedule.rb ならば Rails アプリケーションの config ディレクトリ内に配置できるので、設定ファイルを一元管理する事もできて分かりやすいと思いました。

whenever では他にもメールを飛ばしたりなど、色んな機能があるので、是非 README.md を読んでみてください。

【Linux】xargs コマンドの使い方がよく分からない

概要

結構複雑な処理をサーバーサイドでやりたいと思って検索すると、xargs コマンド使ったワンライナーを良く見かけます。

ただし、xargs コマンドが何者なのか理解することなく、今まで検索したワンライナーをコピペして使ってきました。

折角なので xargs コマンドの使い方をまとめました。

 

xargs とは

前のコマンド (command1) の実行結果を標準入力 (stdin) から受け取って、次のコマンドの引き数に渡す (stdout) 仲介役をしてくれるコマンドです。

f:id:kyamanak83:20180129224458p:plain

参考:

www.atmarkit.co.jp

 

よく見る利用法

よく使われるのは、find コマンドと組み合わせて、名前検索でヒットしたファイルを標準入力から受け取って、削除する方法かな思います。

$ find . -name "*.log" | xargs rm -fv
'./access.log' を削除しました
'./error.log' を削除しました

自分はこのワンライナーで初めて xargs コマンドに触れました。

そして、これなら rm *.log だけで良くない?xargs コマンドを使うメリットってどこにあるの?って混乱したのを覚えています

確かに、このワンライナーと実行結果を見ると rm コマンド単体で問題ありません。

しかし、find コマンドは名前検索以外にも様々な条件でファイルを検索することが出来るので、xargs コマンドと組み合わせると作業効率の幅が広がります。

  • タイムスタンプで検索できる
  • パーミッションで検索できる
  • ファイルサイズで検索できる(空ファイルも検索できる)
  • 検索する階層を指定できる

ネットで検索した xargs コマンドのサンプルには、このようにメリットが感じられないサンプルも多々あります。

それを見て、こうやって使うのか!って鵜呑みにしたことが当時間違えでした。

高速に並列処理できる

find コマンドには exec オプションと言うものがあり、検索でヒットしたファイルを、exec オプション以下のコマンドに渡して実行することができます。

$ find . -name "*.log" -exec rm -fv {} \;
'./access.log' を削除しました
'./error.log' を削除しました

一見、xargs コマンドと同じですが、内部的な処理が違います。

exec オプションを使った場合
$ rm -fv access.log
$ rm -fv error.log

rm コマンドを 2 回叩いたのと同様です

xargs コマンドを使った場合
$ rm -fv access.log error.log

rm コマンドの引数に 2 つのファイルを指定したのと同様です。

この 2 つのコマンドは大量のファイル数を削除する場合に明確な差が出ます。

xargs コマンドを使った方が、複数のファイルを一度に削除してくれるので高速です。

参考:

qiita.com

Argument list too long の制限を受けない

大量のファイルを rm *.log で削除しようとすると、Argument list too long と怒られることがあります。

これはコマンドの引数の文字列が長すぎるせいです。

rm *.log を実行した場合、内部的には rm aaa.log bbb.log ccc.log ddd.log .... を実行しています。ファイル数が多い場合、この文字列はどうしても長くなります。

OS 毎に引数に指定できる文字数は決まっており、その文字数を超えるとエラーとなります。

以下のコマンドでその文字数を確認できます。

$ getconf ARG_MAX

xargs コマンドは、この ARG_MAX の値を見て良い感じに処理を分割して実行してくれるので文字数の制限に引っかからないらしいです。

参考:

qiita.com

 

ドライランオプション

xargs コマンドには ドライランオプションがあります。

前のコマンドの出力結果が不確かな場合や、自分が意図した処理を実行してくれるが心配な場合、p オプションを付けることで生成されるコマンドを確認することができます。

$ find . -name "*.log" | xargs -p rm -fv
rm -fv ./error.log ./access.log?... # 上のコマンドはこのコマンドを叩くのと同様

p オプションは生成されるコマンドを確認するだけで、実際には実行されません。

確認して問題なければ p オプションを外して実行してください。

 

引数の場所を指定する

rm コマンドを実行する場合、引数が 1 つなので問題ありませんが、cp コマンドや mv コマンドを実行する場合は引数が 2 つ必要になります。

そのような場合、i オプションを使って、xargs コマンドで受け取った値を引数に指定する位置を決める必要があります。{} に受け取った値が入ります。

$ find . -name "*.log" | xargs -i cp {} /tmp/.

これは find コマンドで受け取ったファイルを /tmp/ 以下にコピーする例となります。

 

便利なワンライナー

個人的に一番良く使うワンライナーです!

カレントディレクトリ以下のファイルから "hogehoge" という文字列を含むファイルを探し出して、そのファイルの "hogehoge" を全て "fugafuga" に置換してくれます。

$ grep -rl 'hogehoge' ./* | xargs perl -i -pe "s/hogehoge/fugafuga/g" 

自分は Ruby on Rails を好んで開発していますが、アプリケーション内で何度も使うメソッドは app/controllers/application.rb で管理しています。

もし、app/controllers/application.rb 内に書かれている共通メソッドの名前を変更した場合、そのメソッドを呼び出している箇所を全て探し出して vim とかで修正するのは面倒です。

そう言った時にこのワンライナーを使って一発で置換します。

 

最後に

今回は rm コマンドをベースに話しましたが、他のコマンドの引数に渡すことも良くやると思います。

xargs コマンドを使えるようになって、運用面でかなり幅が広がりました。

特にログ調査とかで 1 週間以内のアクセスログから、この URL にリクエストしたユーザ数を抽出するとか、そう言った時に凄く便利です。

最近だと Splunk とかログ解析系の PF が沢山あるので、サーバにログインしてコマンドで解析したりすることは減ってきているかもしれませんが。

サーバーサイドのエンジニアは作業効率化に繋がりますので、使いこなせるように理解して下さい。

【Linux】コマンドの標準出力を色付けして運用を楽しくする

概要

単色の標準出力だと見づらいですし、運用していて楽しくないので、コマンドの標準出力を色付けしてくれる拡張パッケージをまとめました。

 

導入方法

拡張パッケージをインストールして、その後  bashrc か bash_profile に標準コマンドの alius を設定してサーバに反映させようと思います。

 

拡張パッケージ

exa

Rust で書かれた ls コマンドの出力を色付けしてくれるパッケージです。

github.com

はじめに Rust 系製品のパッケージマネージャーである cargo をインストールします。

$ curl https://sh.rustup.rs -sSf | sh
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
1 # 1 を入力して Enter

# インストール確認
$ rustc --version
rustc 1.23.0 (766bd11c8 2018-01-01)

 参考:rustup のインストール方法

※ 環境によって cmake など必要なパッケージがない場合には別途インストールして下さい。exa インストール時に足りないパッケージがエラー出力されると思います。

# cargo 経由でインストール
$ cargo install exa
# インストール確認 $ exa --version exa v0.8.0

 

ls コマンドと exa コマンドの見易さを比較します。

ls コマンド

exa コマンド

f:id:kyamanak83:20180114164714p:plain

f:id:kyamanak83:20180114164728p:plain

 

-T オプションをつけると tree コマンドと同じ標準出力になります。

tree コマンド

exa コマンド ( -T オプション)

f:id:kyamanak83:20180114165738p:plain

f:id:kyamanak83:20180114165259p:plain

colordiff

diff の標準出力を色付けしてくれるパッケージです。そのままのネーミングですね。

https://www.colordiff.org/

$ sudo yum --enablerepo=epel install colordiff

 

colordiff コマンドの場合、追加された行が 、削除された行が で色付けされます。

f:id:kyamanak83:20180114173310p:plain

grc

ifconfig, dig, netstat, ping, ps, traceroute, df, free など運用に欠かせないコマンドの色付けを手助けしてくれます。 

github.com

色付けしてくれるコマンドは grc.bashrc ⬇︎ に記載されているコマンド全部です。

https://github.com/garabik/grc/blob/master/grc.bashrc

※ 環境によって python34 など必要なパッケージがない場合には別途インストールして下さい。grc コマンド実行時にエラー出力されると思います。

$ git clone git@github.com:garabik/grc.git
$ cd grc/
$ sudo ./install.sh

 

一部のコマンドの色付け結果を見てみます。

■ ps コマンド

f:id:kyamanak83:20180114180150p:plain

■ dig コマンド

f:id:kyamanak83:20180114180211p:plain

ccze

tail などと併用してログを色付けして出力してくれるパッケージです。

github.com

$ sudo yum --enablerepo=epel install ccze

ccze コマンドは tail コマンドの結果をパイプで受けて色付けします。

f:id:kyamanak83:20180114181610p:plain

source-highlight

less の結果を色付けしてくれるパッケージです。

GNU Source-highlight - GNU Project - Free Software Foundation (FSF)

$ sudo yum install source-highlight

 

はじめに bashrc か bash_profile に以下の内容を記載することをオススメします。

export LESS='-R'
export LESSOPEN='| /usr/bin/src-hilite-lesspipe.sh %s'

その後、いつも通り less でファイルを開くと色付きで見ることができます。

f:id:kyamanak83:20180114184534p:plain

 

標準コマンドのエイリアスを設定

毎回 grc だったり、ccze だったりを付けるのは面倒くさいので、標準コマンドのエイリアスにしてしまします。

exa コマンドも馴染みがないので ls コマンドのエイリアスにしてしまった方がいいと思います。

.bashrc か .bash_profile に記載して下さい。

/etc/bashrc に記載して全ユーザに適用するのもありだと思います!

# grc を clone した時に入っていた bashrc を読み込む
. ~/grc/grc.bashrc 
# alias dig='grc dig', alias ps='grc ps' などを全てやってくれる

export PATH="$HOME/.cargo/bin:$PATH"
export LESS='-R'
export LESSOPEN='| /usr/bin/src-hilite-lesspipe.sh %s'
tailc () {
    tail $@ | ccze -A
}
alias tail='tailc'
alias ls='exa'

 

最後に

単色の標準出力で長時間作業しているとストレスが溜まってしまうと思うので、是非導入できるサーバには導入して運用を楽にして下さい。

なによりも色付きの標準出力の方が作業していて楽しいと思います!

【Apache】graceful 再起動でホットデプロイを行う

概要

自分のお仕事は運用メインなので、リリース作業を行う機会が多いです。

正直、数年間同じ仕事をしているので、リリース作業は慣れましたし飽きました(苦笑)

そこで、少しでも今のリリース作業が簡略化できる方法が無いか調べてみました。

そこで見つけたのがホットデプロイです。

現在のリリース手順とは、ロードバランサから数台ずつサーバーをサービスアウトしてリリースして戻す(サービスイン)と言うものです。

リリース時にサーバーの再起動(詳しくは Apache の再起動)を行うため、ユーザー影響がないようにサービスアウトしています。

ホットデプロイを導入すると、アプリケーションの修正を行っても、サーバーの再起動を行わずに本番環境に反映させることが出来ます。

と言うことは、ロードバランサから数台ずつサービスアウトせずに、一気に全台リリース出来るようになりますよね??

実現すれば、かなり作業が簡略化されます。

自分が運用しているシステムは LAMP 環境が多いので、LAMP 環境でホットデプロイが出来るか調べてみました。

 

はじめに

LAMP 環境とは?

Web サービスを開発する際の環境の組み合わせで、それぞれの頭文字も取ったものです。

OS  Linux
Webサーバ  Apache
DB  MySQL
プログラミング言語  PHP ( Perl, Python )

参考:LAMP (ソフトウェアバンドル) - Wikipedia

多くの企業で導入実績があり、沢山のモジュールやプラグインが配布されているため、拡張性に優れている気がします。

全てオープンソースソフトウェア(無償)なので個人でも簡単に環境構築できます。

ホットデプロイとは?

アプリケーションに変更を加えた際に、サーバーのサービスアウトと再起動を行わずに運用環境に反映させる仕組みのことです。

詳しくは定義されていないので、サービスアウトと再起動なしで、アプリケーションの更新ができれば、なんでもホットデプロイと呼ばれる気がします。

参考:ホットデプロイとは - IT用語辞典 Weblio辞書

 

ホットデプロイ導入

最初は Ruby on Rails で良く使われている Unicorn みたいに Apache も出来るんじゃないかと思っていました。

誰かしら便利なモジュールを作っていると思ってました。けど無かったです。

調べてみたら、Apache でサービス停止をせずにプロセスを新しく立ち上げ直す方法があったのでそれを使おうと思います。

graceful で再起動

サービスの停止をせずに、アプリケーションの変更を取り入れて、プロセスを新しく立ち上げ直してくれます。

親プロセスは、子プロセスに現在のリクエスト処理が完了したら終了するように命令します。

親プロセスは設定ファイルを再読み込みして、ログファイルを開き直します。

restart と違って親プロセスは終了しないので PID はそのままです。

子プロセスが徐々に無くなるにつれて、新しい設定を反映した子プロセスが立ち上がりリクエストに応答します。

graceful では再起動する前に、設定ファイルの構文チェックをしてくれます。

誤りがあった場合は、エラーメッセージを出力し、サーバーの再起動は行いません。

コマンド
$ sudo apachectl -k graceful

又は

$ sudo /etc/init.d/httpd graceful
Gracefully restarting httpd:

参考:Stopping and Restarting Apache HTTP Server - Apache HTTP Server Version 2.4

注意点

SSL 証明書の更新や、モジュールの追加を行った場合は、graceful では反映されないケースがある様なので注意してください。

owen11.hateblo.jp

 

まとめ

Apache を利用しているサーバーでは graceful を利用することでホットデプロイ出来ることが分かりました。

今までアプリケーションを修正した際には、何でもかんでも(念のため) restart していましたが、 今後は graceful や reload など使い分けたいと思います。

SSL 証明書の更新や、モジュールの追加を伴うリリースは頻度が少ないので、基本は graceful を使おうと思います。

まだ実運用はしていませんが、これで通常のリリース手順を簡略化できたと思います。

ただし、全台一気に行うリリース作業は時間が短縮されましたが、事故リスクは高まりました。

development 環境で開発して、いきなり production 環境へのリリースではなく、staging 環境に一旦リリースして確認することが大事だと思います。