地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Redis】redis setup

Redis 冗長化 & フェールオーバー環境を CentOS 6.x 上に構築する手順です 手順は3部構成となっており、本稿は Redis のインストール方法となります

 (その1):Redisのインストール  (その2):Redisの冗長化  (その3):Redis-Sentinelによるフェールオーバー環境の構築

環境

CentOS release 6.x ・Redis 3.0.7 ・Redis-Sentinel 3.0.7

構成の概要

まずは以下構成で Redis を構築します

起動ファイル ポート 設定ファイル 説明
redis 6379 6379.conf Redis 本体です

Redis のインストール

パッケージから導入する方法と、ソースからインストールする2種類の方法があります 各自の環境制約を踏まえて導入手段を検討してください

パッケージから導入する場合

Redis は標準パッケージに含まれていません(2015/11/15時点) サードパーティリポジトリをインストールするので、標準リポジトリしか利用できない環境では後述の「ソースからインストール」にスキップしてください

サードパーティリポジトリをセットアップします remi と EPEL リポジトリが必要です導入手順はこちら メモリ管理に tcmalloc を利用しているため gperftools-libs もインストールします

$ sudo yum install --enablerepo=epel gperftools
$ yum info --enablerepo=remi redis | egrep "Version|バージョン"
Version : 3.0.7
$ sudo yum install --enablerepo=remi redis

自動起動スクリプトがインストールされるので、実行して正常起動することを確認します

$ sudo service redis start
redis-server を起動中:                                     [  OK  ]
$ ps -ef | grep redis
redis 10995 1 0 21:57 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379

Redis に接続してバージョンを確認します

$ redis-cli info | grep redis_version
redis_version:3.0.7

Redis のワークディレクトリ作成

パッケージ版では設定ファイルの配置先が /etc/ 直下となります Redis 冗長化にあたり設定ファイルは複数作成するので、煩雑回避のため Redis 用のワークディレクトリを作成して集約させます なお redis ユーザが書き込めるよう権限を付与しておきます

 /etc/redis:設定ファイルの格納先

$ sudo mkdir /etc/redis
$ sudo chmod 755 /etc/redis
$ sudo chown redis:redis /etc/redis

自動起動スクリプトの修正

設定ファイルの配置先を変更したので REDIS_CONFIG を変更します 複数プロセス立ち上げるので pidfile もポート番号を付加しておきます

$ sudo vi /etc/init.d/redis
:
#pidfile="/var/run/redis/redis.pid"
#REDIS_CONFIG="/etc/redis.conf"
REDISPORT=6379
pidfile="/var/run/redis/redis_${REDISPORT}.pid"
REDIS_CONFIG="/etc/redis/${REDISPORT}.conf"

Redis 設定ファイルのコピー

ポートごとの設定ファイルにするので元の設定ファイルをコピーします

$ sudo cp -p /etc/redis.conf /etc/redis/6379.conf

ここまで実施したら「Redis 設定ファイルの編集」までスキップしてください

ソースからインストールする場合

Redis のホームページからソースをダウンロードしてインストールします 2016/2/3時点の最新版は、3.0.7 のようです

Redis の冗長構成には redis 2.4.16 または 2.6.0-rc6 以降のバージョンが必要です こちらで適切なバージョンを確認して、以下コマンドのファイル名を変更してください Downlod 一覧

$ cd
$ cd Downlods
$ wget http://download.redis.io/releases/redis-3.0.7.tar.gz
$ tar xzf redis-3.0.*.tar.gz
$ cd redis-3.0.*
$ make
$ sudo make install

make でエラーが発生する場合は、開発ツールをインストールしてください

make[3]: gcc: コマンドが見つかりませんでした
make[3]: *** [net.o] エラー 127

$ sudo yum groupinstall 'Development Tools'

デフォルトのインストール先は、/usr/local/bin/ です 変更したい場合は、redis-3.0.*/src の配下の Makefile を手動で変更します

PREFIX?=インストール先ディレクトリ名

Redis ユーザ及びグループの作成

Redis インスタンスを起動するユーザとグループを作成します ログインは不要なので nologin を指定します

$ sudo groupadd redis
$ sudo useradd -s /sbin/nologin -M -g redis redis

Redis のワークディレクトリ作成

Redis 用のワークディレクトリを作成し、 redis ユーザの書き込み権限を付与します 必要なワークディレクトリは以下となります

 /etc/redis:設定ファイルの格納先  /var/run/redis:データファイルや起動プロセスIDファイルの格納先  /var/log/redis:ログ出力先

$ sudo mkdir /etc/redis /var/run/redis /var/log/redis
$ sudo chmod 755 /etc/redis /var/run/redis /var/log/redis
$ sudo chown redis:redis /etc/redis /var/run/redis /var/log/redis

Redis 設定ファイルのコピー

ソースインストールでは Redis 設定ファイルの雛形が用意されているので格納先ディレクトリにコピーします パッケージ導入では既に配置されています。この章はスキップしてください

$ sudo cp -p ~/Downloads/redis-3.0.*/redis.conf /etc/redis/6379.conf

Redis サーバの起動と停止

Redis 起動シェルスクリプトを引数に設定ファイルを指定し redis ユーザで起動します

$ sudo -u redis sh -c "redis-server /etc/redis/6379.conf --daemonize yes --dir /var/run/redis"

Redis が正常に起動すれば以下のメッセージが画面に出力されます

6251:M 03 Feb 04:46:23.166 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
6251:M 03 Feb 04:46:23.166 # Redis can't set maximum open files to 10032 because of OS error: Operation not permitted.
6251:M 03 Feb 04:46:23.166 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 3.0.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 6251
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

別のターミナルを立ち上げて Redis クライアントから接続してみます 重要:  初期状態では Redis はサービスとして起動していないので、プロンプトは戻ってきません  よって別のターミナルを立ち上げて操作する必要があります

$ redis-cli -p 6379

Redisサーバから PING コマンドで PONG が返却されることを確認します

127.0.0.1:6379> ping
PONG

接続確認ができたら Redis サーバを終了させます 切断後は PING コマンドで PONG が返却されないことを確認してください

 重要  デフォルトの設定では データファイル書き出し先に権限に無いため shutdown コマンドで単純に終了できません  必ず、config set コマンドで dir を指定してから shutdown してください  これは後ほど、設定ファイルで デーモン化 することで対処します

127.0.0.1:6379> config set dir /var/run/redis
127.0.0.1:6379> shutdown
not connected>
not connected> ping
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> exit

ps コマンドでも redis プロセスが出力されないことを確認しましょう

$ ps -e | grep redis
★ 何も表示されないこと

自動起動スクリプトのコピー

ソースからインストールした場合は、自動起動スクリプトを設定する必要があります サーバの再起動でも自動で Redis を起動させるため、用意された自動起動スクリプトをコピーして登録します

$ sudo cp -p ~/Downloads/redis-3.0.*/utils/redis_init_script /etc/init.d/redis

自動起動スクリプトを修正します ヘッダの5行目に chkconfig: 345 70 15 を追記します(下記の最終行です) プロセスIDファイルの配置場所も変更します もしパスワードを設定する場合は、CLIEXEC も変更しておきます

 重要   Redisでは、接続時にセキュリティを高めるためパスワード設定ができます   もしパスワードを設定したら起動スクリプトでもセットが必要です   これをやらないと Redisサーバプロセスを スクリプトから終了させることができません...   対応としては CLIEXEC に -a の引数でパスワードを指定します

$ sudo vi /etc/init.d/redis
#!/bin/sh
#
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
# chkconfig: 345 70 15
:
#CLIEXEC=/usr/local/bin/redis-cli  ★ ここはパスワード設定した場合のみ変更します!!
CLIEXEC="/usr/local/bin/redis-cli -a hoge"
#PIDFILE=/var/run/redis_${REDISPORT}.pid
PIDFILE=/var/run/redis/redis_${REDISPORT}.pid

Redis 設定ファイルの編集

ここからはパッケージ導入でもソース導入でも必要な作業となります Redis サーバの環境設定をおこないます

$ sudo vi /etc/redis/6379.conf
daemonize yes
pidfile redis_6379.pid
port 6379
bind 127.0.0.1
loglevel notice
logfile /var/log/redis/6379.log
dbfilename 6379.rdb
dir /var/run/redis
requirepass hoge
maxclients 1024

各項目の詳細は以下となります  1. daemonize:Redisをデーモンで起動させます  2. pidfile:Redisのプロセスファイルの名前を指定します(ポート番号にあわせてます)  3. port:起動するポート番号を指定します  4. bind:接続可能なサーバのIPアドレスを指定します   重要    コメントアウトまたは'0.0.0.0'で制限解除となります    ただしセキュリティの確保のためにも接続するサーバのみへ制限しておきましょう    本稿では同一サーバから接続するので 127.0.0.1 としています    なお複数のサーバから接続する場合は、スペース区切りでIPアドレスを列挙します    あわせて iptables で接続するサーバのIPとポートを解放する必要があります  5. ログ出力レベルを指定します   検証中は debug または info 、サービス稼働では notice としておきましょう  6. ログファイルの名前を指定します(ポート番号にあわせてます)  7. RDB(永続性担保のスナップショット)のファイル名を指定します(ポート番号にあわせてます)    Redisの魅力としてデータの永続性担保が挙げられます    メモリ内のデータをあるタイミングでファイルに書き出します  8. redis実行時のベースディレクトリを指定します  9. パスワードを指定します   重要    サンプルとして hoge としてますが、わかりにくい適切なパスワードに変更してください  10. 接続できる最大コネクション数を指定します   重要    複数のHTTPサーバから利用する場合は、サーバ台数 × MaxClient などの考慮が必要です    場合によっては最大接続数がデフォルトの 1024 では足りないケースもあります    ただTCP接続ではファイルディスクリプタが利用されるため、OSのファイルオープン数の    制限値を考慮する必要があり、単純に設定値を拡張できません    1024 で足りない場合は後述の Redis 自動起動ファイル内でlimit制限を変更します

Redisの起動設定

Redisサーバの設定後、起動スクリプトで Redis サーバの起動と停止ができることを確認します

$ sudo /etc/init.d/redis start
Starting Redis server...
$ ps -ef | grep redis
root       3328      1  0 05:27 ?        00:00:00 /usr/local/bin/redis-server 127.0.0.1:6379      

Redisに接続します 設定ファイルの requirepass で指定したパスワードを auth コマンドで入力し認証をとおしてから PING コマンドで PONG が返却されることを確認します

$ redis-cli -p 6379
127.0.0.1:6379> auth hoge
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> quit

自動起動スクリプトで終了できることを確認します

$ sudo /etc/init.d/redis stop
Stopping ...
Redis stopped
$ ps -e | grep redis
$ /usr/local/bin/redis-cli -p 6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused

Redisの起動と終了は、以下コマンドでもOKです

$ sudo service redis start
$ sudo service redis restart
$ sudo service redis stop

redis の自動起動(サービス)登録

起動スクリプトで正常に起動と終了ができることを確認してからサービスとして登録します

$ sudo chkconfig redis on
$ chkconfig --list redis
redis  0:off  1:off  2:on  3:on  4:on  5:on  6:off

サーバを再起動させて Redis が自動起動されることを確認します

$ reboot

サーバが再起動したら

$ ps -ef | grep redis
redis      2042      1  0 06:26 ?        00:00:00 /usr/bin/redis-server 127.0.0.1:6379                                        

Redis の冗長化

複数の Redis でレプリケートさせる方法はRedisのフェールオーバー環境を構築する(その2)で説明します

Appendix

iptablesの変更

外部のサーバから Redis に接続する場合は、iptables の設定が必要です。 IPアドレス(帯域指定可)と redis のポートを許可し、iptables サービスを再起動させて反映します

$ sudo vi /etc/sysconfig/iptables
-A INPUT -p tcp -m tcp -s 128.0.0.0/24 --dport 6379 -j ACCEPT
$ sudo /etc/init.d/iptables restart

メモリオーバーコミットの設定

redis の起動時に以下メッセージがログに表示される場合があります

# WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
    [15050] 16 Jan 22:17:28.303 * DB loaded from disk: 0.000 seconds

Linux では実メモリが足りなくなり、溢れたスワップ領域も使い切ってしまうとメモリを 確保しているプロセスを強制終了させる仕組みがあります(OOMKillerと呼ばれてます)

0:空き容量がなければ実行中のプロセスを強制終了する(デフォルト)
1:制限ギリギリまでメモリがあるように振る舞い、確保できなければ実行中のプロセスを強制終了する
2:空き容量がない場合はエラーを発生させる

redis はデータ永続化(スナップショットやAOFログ保存)の際、格納データの2倍のメモリを利用するので もしメモリを使い切ってしまい、確保(allocate)に失敗した場合は エラーが発生します この時、OOMKiller の設定によっては redis プロセスが強制終了されます

ワーニングメッセージでは vm.overcommit_memory = 1 をセットと表示されていますが 本来ならエラーを誘発させアプリ側でエラートラップすべきなので、"2"をセットします (ここ解釈を間違えてるかもしれません。詳しい方教えてくださいませ)

なおシステム設定ファイルはバックアップを取ってから変更します また変更後はサーバの再起動が必要です

$ sudo cp -p /etc/sysctl.conf /etc/sysctl.conf_`date '+%Y%m%d'`
$ sudo vi /etc/sysctl.conf
vm.overcommit_memory = 2

$ sudo shutdown -r now

crackit/crack@redis.io

redis コマンドの “config set dbfilename” 機能を悪用した、redis 起動ユーザ権限で任意のファイルが作成できる攻撃手法が公開されています。 (= sshの公開鍵を配置してリモートログイン用の穴を開けてしまう)

redis著者による攻撃考察はこちら

原因は bind の設定忘れで、もし攻撃を受けると、crackit というキーと、バリューに sshキーとcrack@redis.io が登録されます。(見ればすぐヤバイと感じます)

他サーバとの接続検証などで、bind設定(IPアドレス制限)を解放した場合、その後閉じることを忘れずに実施することが重要です

対処方法は 1. redis.conf の bind でIPを制限する(閉じる)  接続できるサーバのIPアドレスを指定し、redisを再起動する

$ sudo vi /etc/redis/redis.conf
bind  127.0.0.1
$ sudo /etc/init.d/redis restart
  1. iptables でredisのポートを閉じる
    redisのポートにアクセスできるIPアドレスのみ指定し、iptabelsを再起動する
$ sudo vi /etc/sysconfig/iptables

 [IP帯域で許可する場合]
  -A INPUT -p tcp -m tcp -s [接続させるIP帯域] –dport 6379 -j ACCEPT

 [ポート閉塞する場合]
  6397の行をコメントアウト(もしくは6379関連は記載しない)

$ sudo /etc/init.d/iptables restart

以上です