end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

install torch

lua言語のinstallが完了したので、次は、torch.

Torch | Getting started with TorchTorch7 のインストール - のんびりしているエンジニアの日記 を 参考に(というより、そのまま)実行。

途中で、libreadline.so.8 を見つけられないエラーとなった為、 「export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib」を行い、再実行。

$ git clone https://github.com/torch/distro.git ~/torch --recursive
$ cd ~/torch; bash install-deps;
$ ./install.sh
   :
  CMAKE_REQUIRED_LIBRARIES is set to:
    /usr/lib64/libdl.so
  For compatibility with CMake 3.11 and below this check is ignoring it.
Call Stack (most recent call first):
  /usr/local/share/cmake-3.15/Modules/FindThreads.cmake:128 (CHECK_INCLUDE_FILE)
  exe/luajit-rocks/luajit-2.1/CMakeLists.txt:174 (FIND_PACKAGE)
This warning is for project developers.  Use -Wno-dev to suppress it.
which: no nvcc in (/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/end0tknr/.local/bin:/home/end0tknr/bin)
/home/end0tknr/tmp/torch/install/bin/luajit: error while loading shared libraries: libreadline.so.8: cannot open shared object file: No such file or directory
Installing common Lua packages
/home/end0tknr/tmp/torch/install/bin/luajit: error while loading shared libraries: libreadline.so.8: cannot open shared object file: No such file or directory
$
  :
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
$ ./install.sh
   :
Do you want to automatically prepend the Torch install location
to PATH and LD_LIBRARY_PATH in your /home/end0tknr/.bashrc? (yes/no)
[yes] >>> 
yes
$

何やら、.bashrc に書き込まれたらしので、内容を確認すると、 torch-activate を呼び出し、その中で、環境変数を登録するようになっていました。

$ cat ~/.bashrc
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
. /home/end0tknr/tmp/torch/install/bin/torch-activate


$ cat ~/torch/install/bin/torch-activate
export LUA_PATH='/home/end0tknr/.luarocks/share/lua/5.1/?.lua;/home/end0tknr/.luarocks/share/lua/5.1/?/init.lua;/home/end0tknr/tmp/torch/install/share/lua/5.1/?.lua;/home/end0tknr/tmp/torch/install/share/lua/5.1/?/init.lua;./?.lua;/home/end0tknr/tmp/torch/install/share/luajit-2.1.0-beta1/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua'
export LUA_CPATH='/home/end0tknr/.luarocks/lib/lua/5.1/?.so;/home/end0tknr/tmp/torch/install/lib/lua/5.1/?.so;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so'
export PATH=/home/end0tknr/tmp/torch/install/bin:$PATH
export LD_LIBRARY_PATH=/home/end0tknr/tmp/torch/install/lib:$LD_LIBRARY_PATH
export DYLD_LIBRARY_PATH=/home/end0tknr/tmp/torch/install/lib:$DYLD_LIBRARY_PATH
export LUA_CPATH='/home/end0tknr/tmp/torch/install/lib/?.so;'$LUA_CPATH

lua 言語を sourc から install

依存libraryである readline や ncurse を installした上で、 http://www.lua.org/download.html を参照し、進めましたが、 「make linux test」でエラー(以下)。

$ https://www.lua.org/ftp/lua-5.3.5.tar.gz
$ tar -xvf lua-5.3.5.tar.gz
$ cd lua-5.3.5
$ make linux test
   :
gcc -std=gnu99 -o lua   lua.o liblua.a -lm -Wl,-E -ldl -lreadline 
//usr/local/lib/libreadline.so: undefined reference to `tputs'
//usr/local/lib/libreadline.so: undefined reference to `tgoto'
//usr/local/lib/libreadline.so: undefined reference to `tgetflag'
//usr/local/lib/libreadline.so: undefined reference to `UP'
//usr/local/lib/libreadline.so: undefined reference to `tgetent'
//usr/local/lib/libreadline.so: undefined reference to `tgetnum'
//usr/local/lib/libreadline.so: undefined reference to `PC'
//usr/local/lib/libreadline.so: undefined reference to `tgetstr'
//usr/local/lib/libreadline.so: undefined reference to `BC'
collect2: error: ld returned 1 exit status
make[2]: *** [lua] Error 1
make[2]: Leaving directory `/home/end0tknr/tmp/lua-5.3.5/src'
make[1]: *** [linux] Error 2
make[1]: Leaving directory `/home/end0tknr/tmp/lua-5.3.5/src'
make: *** [linux] Error 2
$

Lua を一般ユーザでソースコードからインストールする - はちゅにっきInstall Lua From Source によれば、 MYCFLAGS , MYLDFLAGS , MYLIBS を 指定すれば良いらしく、結果、解消。

$ make linux MYCFLAGS="-I/usr/local/include" \
       MYLDFLAGS="-L/usr/local/lib" \
       MYLIBS="-lncursesw"
$ sudo make install INSTALL_TOP=/usr/local/lua

mysql8で、rootパスワードの再設定

http://www-creators.com/archives/5574

以下に記載していますが、内容は上記urlの通り、手元の環境にあるmysql8は、 /etc/my.cnf に skip-grant-tables を追記することで、 mysqlへのrootログインをパスワードなしに設定していた為、再確認。

STEP1 - mysqlへのパスワードなしログイン設定

my.cnf の [mysqld] 領域に skip-grant-tables を追記することで "$ /usr/local/mysql/bin/mysq -u root"のようにパスワードなしで ログインできるようにします。

$ sudo vi /etc/my.cnf
   [mysqld]
   skip-grant-tables

$ sudo systemctl restart mysqld.service

STEP2 - mysqlのrootパスワードをnull化

$ /usr/local/mysql/bin/mysql -u root
mysql> use mysql;
mysql> UPDATE user SET authentication_string=null WHERE user='root';
mysql> flush privileges;
mysql> quit;

$ sudo systemctl restart mysqld.service

STEP3 - mysqlへのパスワードなしログイン設定を解除

$ sudo vi /etc/my.cnf
   [mysqld]
   # skip-grant-tables

$ sudo systemctl restart mysqld.service

STEP4 - mysqlのrootパスワードを再?設定

$ /usr/local/mysql/bin/mysql -u root
mysql> use mysql;
mysql> ALTER USER 'root'@'localhost' identified BY 'ないしょ';

Tomcat8のsession replicationによる複数host間でのセッション情報共有

初めて立て見みましたが、やってみると、ほぼ、tomcatのdocumentのまんま。という感じ

全体構成

┌PC─────┐
│┌────┐│
││browser ││
│└──┬─┘│
└───│──┘
        │
┌ HostA│──┐              ┌ HostB───┐
│┌──┴─┐│              │            │
││nginx   ││ Load Balance │            │
│└──┬─┘│ (Round Robin)│            │
│      ├─────────────┐      │
│┌──┴─┐│              │┌─┴──┐│
││tomcat8 ├─────────┤tomcat8 ││
│└──┬─┘│ Session      │└─┬──┘│
│┌──┴─┐│ Replication  │┌─┴──┐│
││java    ││(DeltaManager ││java    ││
││servlet ││ MultiCast)   ││servlet ││
│└────┘│              │└────┘│
└──────┘              └──────┘

install & setup nginx

ロードバランサとして使用しますが、まずはinstall

[HOST A]$ sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
[HOST A]$ sudo yum install nginx

次に設定。 centos7ha1.a5.jp:80 で受けたrequestを centos7ha1.a5.jp:8080 , centos7ha2.a5.jp:8080 に対し、 round robinでload balanceします。 また、access.log に対し、load balance先のipを出力しています。($upstream_addr)

[HOST A]$ sudo vi /etc/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

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" "$upstream_addr"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

    upstream centos7ha1.a5.jp {
        server centos7ha1.a5.jp:8080;
        server centos7ha2.a5.jp:8080;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://centos7ha1.a5.jp;
        }
    }
}

nginxを起動し、ブラウザでアクセスすると、 centos7ha1.a5.jp:8080 , centos7ha2.a5.jp:8080 に対し、 round robinでload balanceされていることが分かります。

[HOST A]$ sudo systemctl start nginx
[HOST A]$ sudo tail -f /var/log/nginx/access.log
[19/Sep/2019:10:04:49 +0900] "GET / HTTP/1.1" <略> "192.168.63.16:8080"
[19/Sep/2019:10:04:50 +0900] "GET / HTTP/1.1" <略> "192.168.63.17:8080"
   :

その他、今回は各ホストで名前解決する為、/etc/hostsを以下のようにしています。

[ALL HOST]$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.63.16  centos7ha1.a5.jp
192.168.63.17  centos7ha2.a5.jp

install java & tomcat

[ALL HOST]$ sudo yum install java
[ALL HOST]$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
[ALL HOST]$ sudo useradd tomcat
[ALL HOST]$ sudo passwd tomcat
[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ mkdir local
[ALL HOST]$ cd local
[ALL HOST]$ wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.53/bin/apache-tomcat-8.0.53.tar.gz
[ALL HOST]$ tar -xvf apache-tomcat-8.5.45.tar.gz
[ALL HOST]$ ln -s apache-tomcat-8.5.45 tomcat

config tomcat

https://tomcat.apache.org/tomcat-8.0-doc/cluster-howto.html

上記urlを参考に、DeltaManager + MultiCastな Session Replication環境を構築します。 (MultiCast以外にUniCastなSession Replicationもできるようです)

まずは、マルチキャストIPを各hostが受信するようにroutingを設定。

[ALL HOST]$ sudo vi /etc/sysconfig/network-scripts/route-enp0s8
ADDRESS0=224.0.0.0
NETMASK0=240.0.0.0
[ALL HOST]$ sudo service network restart
[ALL HOST]$ netstat -rn
[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ vi /home/tomcat/local/tomcat/conf/server.xml
以下を追記。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
         channelSendOptions="8">
  <Manager className="org.apache.catalina.ha.session.DeltaManager"
           expireSessionsOnShutdown="false"
           notifyListenersOnReplication="true"/>
  <Channel className="org.apache.catalina.tribes.group.GroupChannel">
    <Membership className="org.apache.catalina.tribes.membership.McastService"
                address="228.0.0.4"
                port="45564"
                frequency="500"
                dropTime="3000"/>
    <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
              address="auto"
              port="4000"
              autoBind="100"
              selectorTimeout="5000"
              maxThreads="6"/>
    <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
      <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
    </Sender>
    <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
    <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
  </Channel>

  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
         filter=""/>
  <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

  <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
            tempDir="/tmp/war-temp/"
            deployDir="/tmp/war-deploy/"
            watchDir="/tmp/war-listen/"
            watchEnabled="false"/>
  <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>

動作確認用 java servletの作成

/SetSession で日時をセッションに登録し、 /GetSession でセッションにある情報を画面表示させます。(以下)

package jp.end0tknr;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/SetSession")
public class SetSession extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public SetSession() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        HttpSession session = request.getSession();
        
        Date date = new Date(); // 今日の日付
        String strDate = date.toString();        
        session.setAttribute("sessionKey", strDate);
        
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello !! Create Session !!</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    
    }
}
package jp.end0tknr;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/GetSession")
public class GetSession extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public GetSession() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("sessionValue = " + request.getSession().getAttribute("sessionKey"));
        out.println("<br>");
        out.println(request.getSession().getId());
        out.println("</body></html>");        
        out.close();
    }
}

以下は、servlet用のweb.xmlの抜粋。 web.xmlに「」があると、セッションがレプリカされます。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee">
  <display-name>SessionTest</display-name>
  <distributable />
</web-app>

後は、tomcatを起動し、動作確認して下さい。

[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ cd local/tomcat
[ALL HOST]$ ./bin/startup.sh 

pacemaker + corosync + drbd による mysql HA構成

この手のインフラよりな話は苦手ですが、いろいろと読み漁って、 手元のcentos7で試してみました。

全体構成

  • dev.mysql.com にも紹介されていたこともあり、データのsyncには mysqlのreplicationを利用せず、drbdを使用します。

https://dev.mysql.com/doc/refman/5.6/ja/ha-drbd.html

  • drbdやmysqlの起動停止は、pacemaker に担当させます

  • 今回、mysqlのHA構成を実施していますが、apacheやsambaのHA構成も可能かと思います。

構成図

                        │192.168.63.100(VIP by pacemaker)
 active 192.168.63.16 ┌┴─────────┐192.168.63.17 stand by
┌──────────┴──┐          ┌─┴───────────┐
│CentOs7Ha1                │          │CentOs7Ha2                │
│┌───────────┐│ 相互監視 │┌───────────┐│
││1-A)pacemaker+corosync│←─────→│2-A)pacemaker+corosync││
│└┬─────────┬┘│          │└┬─────────┬┘│
│┌┴────┐┌───┴┐│          │┌┴───┐┌────┴┐│
││1-B)mysql ││1-C)DRBD││          ││2-C)DRBD││2-B)mysql ││
│└───┬─┘└───┬┘│          │└┬───┘└─┬───┘│
│        └------┐    │  │          │  │    ┌------┘        │
│┌─────┐┌┴──┴┐│一方向sync│┌┴──┴┐┌─────┐│
││centos7   ││1-D)DISK│──────→│2-D)DISK││centos7   ││
│└─────┘└────┘│(fail back│└────┘└─────┘│
└─────────────┘  なし)   └─────────────┘

pacemaker と corosync

linux-had proj で開発されていた heartbeatの後継で、 現在 ClusterLabs ( clusterlabs.org )から配布.

corosync がノード死活監視を行い、node down時に、 pacemakerがリソース制御(mysqlやdrbd等の起動や、VIPの付替え等)を行います。 (pacemaker が mysqlの起動/停止を行う為、systemctlへの登録は不要です)

drbd

node間のデータsyncに利用します。 drbdは、diskの指定したパーティションを即時にsecondaryなnodeへコピーします。 drbdが管理するパーティションに対し、本物のディスクのようにアクセスできます。

参考url

全く初めて触りましたので、いろいろと読み漁りました。

作業をすすめる上での注意点

以下に手順を記載しますが...

  • 全ホストへの作業が必要な場合と、1つのホストのみへの作業があります。[ALL HOST]等と記載
  • pacemakerやdrbdのinstall順には注意が必要で、間違えると、syncできないなどの問題が発生します。本エントリの順に作業すると良いと思います。(多分)

各ホスト IPアドレス等のNW系設定

今回、virtual boxで試していますが、 virtual boxのNATネットワーク + ホストオンリーアダプタを利用し、 更にcentos側で以下のようにNW設定しています。

[ALL HOST]$ sudo vi /etc/sysconfig/network-scripts/ifcfg-enp0s8

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
#BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=no
IPV6_AUTOCONF=no
IPV6_DEFROUTE=no
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=enp0s8
UUID=b0f60e72-6181-489e-a077-4b0d547f235a
DEVICE=enp0s8
ONBOOT=yes

[ALL HOST]$ sudo vi /etc/selinux/config
SELINUX=disabled

[ALL HOST]$ sudo systemctl stop    firewalld
[ALL HOST]$ sudo systemctl disable firewalld
                            
[ALL HOST]$ sudo vi /etc/hosts
192.168.63.16  centos7ha1.a5.jp
192.168.63.17  centos7ha2.a5.jp

DRDB の yum install や 設定

elrepo リポジトリの登録

公開鍵の入手と、elrepo リポジトリの登録

[ALL HOST]$ sudo rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
[ALL HOST]$ sudo rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

DRDB の yum install

  • このエントリを記載する前に、sourcからのinstallを試しましたが、依存ライブラリが多い為か、エラーが頻発したので、パッケージでinstallしています。
  • 2019/9時点でdrbdの最新はver.9.0ですが、私の環境(centos7)ではうまく動作させることができなかった為、ver.8.4を使用しています
[ALL HOST]$ sudo yum install kmod-drbd84 drbd84-utils

DRDB の設定 (その1/2)

以下のように global_common.conf の変更と、r0.res の新規作成を行います。

※この設定を行い、更にこの後のdisk追加を行うまで、DRDBを起動する必要はありません。

global_common.conf には、同期レプリケーションである「protocol C」の追記のみ実施

[ALL HOST]$ sudo vi /etc/drbd.d/global_common.conf

global {
   usage-count yes;
   udev-always-use-vnr; # treat implicit the same as explicit volumes
}

common {
   handlers {
   }
   startup {
   }
   options {
   }
   disk {
   }
   net {
        protocol C;
   }
}

diskをsyncする単位で *.resファイルを作成します。

※ 今回「r0」というリソース名にしていますが、リソース名は任意のものを設定できます。

[ALL HOST]$ sudo vi /etc/drbd.d/r0.res

resource r0 {
    meta-disk internal;
    device /dev/drbd0;
    # ↓こちらの「/dev/sdb1」は追加したdiskのpartitionにより異なります
    disk   /dev/sdb1;

    on centos7ha1.a5.jp {
            address 192.168.63.16:7788;
    }
    on centos7ha2.a5.jp {
            address 192.168.63.17:7788;
    }
}

disk(パーティション)の追加

centos側での追加手順

「/dev/sdb (3G)」が DRBD用に追加したdiskです。 diskは認識されているが、領域(partition)が確保できていないこと (sbd1がない)が分かります。

[ALL HOST]$ lsblk -pi
NAME                        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
/dev/sda                      8:0    0   25G  0 disk 
|-/dev/sda1                   8:1    0    1G  0 part /boot
`-/dev/sda2                   8:2    0   24G  0 part 
  |-/dev/mapper/centos-root 253:0    0 21.5G  0 lvm  /
  `-/dev/mapper/centos-swap 253:1    0  2.5G  0 lvm  [SWAP]
/dev/sdb                      8:16   0    3G  0 disk 
/dev/sr0                     11:0    1 1024M  0 rom  

fdiskによるパーティション作成. (対話式PGで迷うことなく追加できます)

[ALL HOST]$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table
Building a new DOS disklabel with disk identifier 0x7ebbb270.

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 
First sector (2048-6291455, default 2048): 
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-6291455, default 6291455): 
Using default value 6291455
Partition 1 of type Linux and of size 3 GiB is set

Command (m for help): W
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.

diskが認識されていることを再チェック. (diskは認識され、領域(partition)も確保済)

[ALL HOST]$ lsblk -pi
NAME                        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
/dev/sda                      8:0    0   25G  0 disk 
|-/dev/sda1                   8:1    0    1G  0 part /boot
`-/dev/sda2                   8:2    0   24G  0 part 
  |-/dev/mapper/centos-root 253:0    0 21.5G  0 lvm  /
  `-/dev/mapper/centos-swap 253:1    0  2.5G  0 lvm  [SWAP]
/dev/sdb                      8:16   0    3G  0 disk 
`-/dev/sdb1                   8:17   0    3G  0 part 
/dev/sr0                     11:0    1 1024M  0 rom  

ただし、この時点でも df -h で /dev/sdb1 が認識されていないことが分かります。 この後の drbdadm コマンドで /dev/sdb1 認識等を行います。

[ALL HOST]$ df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   22G  1.1G   21G   5% /
devtmpfs                 1.9G     0  1.9G   0% /dev
tmpfs                    1.9G     0  1.9G   0% /dev/shm
tmpfs                    1.9G  8.6M  1.9G   1% /run
tmpfs                    1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/sda1               1014M  144M  871M  15% /boot
tmpfs                    379M     0  379M   0% /run/user/1000
tmpfs                    379M     0  379M   0% /run/user/0

DRDB の設定 (その2/2)

DRDBのメタデータ作成

以下のコマンドで、/dev/sdb1内にDRDBのメタデータが作成されるようです。 (DRDBのメタデータの内容は理解できていません)

[ALL HOST]$ sudo drbdadm create-md r0
initializing activity log
initializing bitmap (160 KB) to all zero
Writing meta data...
New drbd meta data block successfully created.
success
$

DRDBの起動と、CentOs7Ha1のprimary設定

以下のコマンドで DRDB を起動すると、各hostがsecondaryとして起動します。

[ALL HOST]$ sudo drbdadm up r0
  --==  Thank you for participating in the global usage survey  ==--
The server's response is:
you are the 68954th user to install this version


[ALL HOST]$ cat /proc/drbd
version: 8.4.11-1 (api:1/proto:86-101)
GIT-hash: 66145a308421e9c124ec391a7848ac20203bb03c build by mockbuild@, .....
 0: cs:Connected ro:Secondary/Secondary ds:Inconsistent/Inconsistent C r-----
    ns:0 nr:0 dw:0 dr:0 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:3144572

「Thank you ~」のメッセージは、drbdadm が LINBIT と通信し、表示しているようですが 気にする必要はないはずです。

次に、centos7ha1.a5.jp で以下のコマンドを実行し、primaryに昇格させます。

[centos7ha1.a5.jp]$ sudo drbdadm primary r0 --force

systemctlにより drbdデーモンを起動します。 (本番?環境においては、pacemakerがdrbdの起動を管理する為、systemctl start は不要)

[ALL HOST]$ sudo systemctl start drbd.service
[ALL HOST]$ sudo drbdadm attach r0

deviceのフォーマットと mount

centos7ha1.a5.jp に対し、以下の各コマンドを実行すると、df -hでも認識されます。

[centos7ha1.a5.jp]$ sudo mkfs.xfs /dev/drbd0
meta-data=/dev/drbd0             isize=512    agcount=4, agsize=196536 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=786143, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

[ALL HOST]$ sudo mkdir /drbd_data
[centos7ha1.a5.jp]$ sudo mount /dev/drbd0 /drbd_data

[centos7ha1.a5.jp]$ df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   22G  1.1G   21G   5% /
devtmpfs                 1.9G     0  1.9G   0% /dev
tmpfs                    1.9G     0  1.9G   0% /dev/shm
tmpfs                    1.9G  8.6M  1.9G   1% /run
tmpfs                    1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/sda1               1014M  144M  871M  15% /boot
tmpfs                    379M     0  379M   0% /run/user/1000
tmpfs                    379M     0  379M   0% /run/user/0
/dev/drbd0               3.0G   33M  3.0G   2% /drbd_data

/drbd_data ( /dev/drbd0 )への書込みテスト

試しに 500GBのファイルを作成します

[centos7ha1.a5.jp]$ cd /drbd_data
[centos7ha1.a5.jp]$ sudo dd if=/dev/zero of=1G.dummy bs=1M count=500
[centos7ha1.a5.jp]$ df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   22G  1.1G   21G   5% /
devtmpfs                 1.9G     0  1.9G   0% /dev
tmpfs                    1.9G     0  1.9G   0% /dev/shm
tmpfs                    1.9G  8.5M  1.9G   1% /run
tmpfs                    1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/sda1               1014M  144M  871M  15% /boot
tmpfs                    379M     0  379M   0% /run/user/1000
/dev/drbd0               3.0G  533M  2.5G  18% /drbd_data

centos7ha2.a5.jp へ syncされたことを確認する為、 centos7ha1.a5.jp 側で umount や secondary化を行います。

[centos7ha1.a5.jp]$ cd /  ## ← /drbd_data にいる状態では umount できない為
[centos7ha1.a5.jp]$ sudo umount /drbd_data
[centos7ha1.a5.jp]$ sudo drbdadm secondary r0

次にcentos7ha2.a5.jp 側で primary化し、mount すると、syncされた結果を確認できます

[centos7ha2.a5.jp]$ sudo drbdadm primary r0
[centos7ha2.a5.jp]$ sudo mount /dev/drbd0 /drbd_data
[centos7ha2.a5.jp]$ ls -l /drbd_data
total 512000
-rw-r--r-- 1 root root 524288000 Sep 14 19:26 1G.dummy

sync結果を確認後、centos7ha1.a5.jp を primaryに戻しておきます

[centos7ha2.a5.jp]$ cd /  ## ← /drbd_data にいる状態では umount できない為
[centos7ha2.a5.jp]$ sudo umount /drbd_data
[centos7ha2.a5.jp]$ sudo drbdadm secondary r0

[centos7ha1.a5.jp]$ sudo drbdadm primary r0
[centos7ha1.a5.jp]$ sudo mount /dev/drbd0 /drbd_data

ここまでは、drbdのinstallと設定で、以降、mysql のinstallに続きます。

mysql ( mariadb ) のinstall

mysql ( mariadb ) のinstall

[ALL HOST]$ sudo yum install mariadb-server mariadb

mysql ( mariadb ) 用 datadirの準備

mysqlのdatadirの準備ができたら、最後にumount

[centos7ha1.a5.jp]$ sudo mkdir /drbd_data/mysql_data
[centos7ha1.a5.jp]$ sudo mysql_install_db --datadir=/drbd_data/mysql_data --user=mysql

[centos7ha1.a5.jp]$ sudo umount /drbd_data

mysql ( mariadb ) の設定

[ALL HOST]$ sudo vi /etc/my.cnf

[mysqld]
symbolic-links=0
bind_address            = 0.0.0.0
datadir                 = /drbd_data/mysql_data
pid_file                = /var/run/mariadb/mysqld.pid
socket                  = /var/run/mariadb/mysqld.sock

[mysqld_safe]
bind_address            = 0.0.0.0
datadir                 = /drbd_data/mysql_data
pid_file                = /var/run/mariadb/mysqld.pid
socket                  = /var/run/mariadb/mysqld.sock

!includedir /etc/my.cnf.d

mysqlのinstallや設定は、一旦、ここまでとし、create database等は後程、行います。

pcs, pacemaker, corosync の install

installと起動

依存関係から、以下により pcs, pacemaker, corosync がまとめてinstallされます。

[ALL HSOT]$ sudo yum install pcs

先程のinstallにより hacluster ユーザが作成される為、passwdを設定します。 また、pcsd の自動起動等も設定します。

[ALL HSOT]$ sudo passwd hacluster
[ALL HSOT]$ sudo systemctl enable pcsd
[ALL HSOT]$ sudo systemctl start  pcsd

クラスタ間の認証と、クラスタ作成

[centos7ha1.a5.jp]$ sudo pcs cluster auth centos7ha1.a5.jp centos7ha2.a5.jp \
                         -u hacluster -p sasa24ki
centos7ha2.a5.jp: Authorized
centos7ha1.a5.jp: Authorized

[centos7ha1.a5.jp]$ sudo pcs cluster setup --name mycluster \
                         centos7ha1.a5.jp centos7ha2.a5.jp --force
Destroying cluster on nodes: centos7ha1.a5.jp, centos7ha2.a5.jp...
centos7ha2.a5.jp: Stopping Cluster (pacemaker)...
centos7ha1.a5.jp: Stopping Cluster (pacemaker)...
centos7ha2.a5.jp: Successfully destroyed cluster
centos7ha1.a5.jp: Successfully destroyed cluster

Sending 'pacemaker_remote authkey' to 'centos7ha1.a5.jp', 'centos7ha2.a5.jp'
centos7ha2.a5.jp: successful distribution of the file 'pacemaker_remote authkey'
centos7ha1.a5.jp: successful distribution of the file 'pacemaker_remote authkey'
Sending cluster config files to the nodes...
centos7ha1.a5.jp: Succeeded
centos7ha2.a5.jp: Succeeded

Synchronizing pcsd certificates on nodes centos7ha1.a5.jp, centos7ha2.a5.jp...
centos7ha2.a5.jp: Success
centos7ha1.a5.jp: Success
Restarting pcsd on the nodes in order to reload the certificates...
centos7ha2.a5.jp: Success
centos7ha1.a5.jp: Success

[centos7ha1.a5.jp]$ sudo pcs cluster start --all
centos7ha1.a5.jp: Starting Cluster (corosync)...
centos7ha2.a5.jp: Starting Cluster (corosync)...
centos7ha2.a5.jp: Starting Cluster (pacemaker)...
centos7ha1.a5.jp: Starting Cluster (pacemaker)...

[centos7ha1.a5.jp]$ sudo pcs status
Cluster name: mycluster

WARNINGS:
No stonith devices and stonith-enabled is not false

Stack: corosync
Current DC: centos7ha2.a5.jp (version 1.1.19-8.el7_6.4-c3c624ea3d) - partition with quorum
Last updated: Sat Sep  7 20:14:02 2019
Last change: Sat Sep  7 20:13:36 2019 by hacluster via crmd on centos7ha2.a5.jp

2 nodes configured
0 resources configured

Online: [ centos7ha1.a5.jp centos7ha2.a5.jp ]

No resources


Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled

クラスタ作成により、/etc/corosync/corosync.conf が作成されます

[ALL HSOT]$ cat /etc/corosync/corosync.conf
totem {  version: 2
         cluster_name: mycluster
         secauth: off
         transport: udpu
}
nodelist {
    node { ring0_addr: centos7ha1.a5.jp
           nodeid: 1
    }
    node { ring0_addr: centos7ha2.a5.jp
           nodeid: 2
    }
}
quorum {   provider: corosync_votequorum
           two_node: 1
}
logging {  to_logfile: yes
           logfile: /var/log/cluster/corosync.log
           to_syslog: yes
}

クラスタの起動

クラスタの起動と、その状態確認は以下の通り

[centos7ha1.a5.jp]$ sudo pcs cluster start --all
centos7ha1.a5.jp: Starting Cluster (corosync)...
centos7ha2.a5.jp: Starting Cluster (corosync)...
centos7ha1.a5.jp: Starting Cluster (pacemaker)...
centos7ha2.a5.jp: Starting Cluster (pacemaker)...


[ALL HOST]$ sudo pcs status
Cluster name: mycluster

WARNINGS:
No stonith devices and stonith-enabled is not false

Stack: corosync
Current DC: centos7ha2.a5.jp (version 1.1.19-8.el7_6.4-c3c624ea3d) - partition with quorum
Last updated: Sat Sep 14 20:14:39 2019
Last change: Sat Sep 14 20:14:23 2019 by hacluster via crmd on centos7ha2.a5.jp

2 nodes configured
0 resources configured

Online: [ centos7ha1.a5.jp centos7ha2.a5.jp ]

No resources

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled


[ALL HOST]$ sudo pcs status corosync
Membership information
----------------------
    Nodeid      Votes Name
         1          1 centos7ha1.a5.jp (local)
         2          1 centos7ha2.a5.jp

pacemaker の 設定

## 現在の設定をfileに保存
[centos7ha1.a5.jp]$ sudo pcs cluster cib clust_cfg

## stonith (不安定ノードの電源断による強制停止/再起動機能)は無効
[centos7ha1.a5.jp]$ pcs property set stonith-enabled=false

## quorum の無効化
## (過半数のノードが参加しているグループに決定権を与える仕組み。
##  2ノード構成では使えない)

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg property set no-quorum-policy=ignore

## failback を抑制
## (「failback」を有効にすると、障害復旧時のリカバリ手順?が複雑になると考えた為)
[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource defaults resource-stickiness=200

インターネットで検索すると、「resource-stickiness=INFINITY」としているケースを 多く見ますが、今後、きちんと調べた方がよいかもしれません。

DRBD の resource として r0 (/etc/drbd.d/r0.res ) を指定

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource create mysql_data ocf:linbit:drbd \
                         drbd_resource=r0 op monitor interval=30s
                         
[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource master MySQLClone mysql_data \
                         master-max=1 master-node-max=1 \
                         clone-max=2 clone-node-max=1 \
                         notify=true

mysql_fs という名前で /dev/drbd0 を /drbd_data にマウントする resource を定義

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource create mysql_fs Filesystem \
                         device="/dev/drbd0" directory="/drbd_data" \
                         fstype="xfs"

MySQLClone には mysql_fs が必須

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint colocation add mysql_fs \
                         with MySQLClone INFINITY with-rsc-role=Master

MySQLClone を master に昇格させる際、 mysql_fs を開始

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint order promote MySQLClone \
                         then start mysql_fs

mysql_service resource の作成、mariadb (mysql) の起動設定

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource create mysql_service \
                         ocf:heartbeat:mysql \
                         binary="/usr/bin/mysqld_safe" \
                         config="/etc/my.cnf" \
                         datadir="/drbd_data/mysql_data" \
                         pid="/var/lib/mysql/mysql.pid" \
                         socket="/var/lib/mysql/mysql.sock" \
                         additional_parameters="--bind-address=0.0.0.0" \
                         op start timeout=60s \
                         op stop timeout=60s \
                         op monitor interval=20s timeout=30s

mysql_fs resource 起動しているnodeで mysql_service を起動

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint colocation add mysql_service \
                         with mysql_fs INFINITY

mysql_fs の後に mysql_service 開始

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint order mysql_fs then mysql_service
Adding mysql_fs mysql_service (kind: Mandatory)
(Options: first-action=start then-action=start)

VIP resource を定義

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource create mysql_VIP ocf:heartbeat:IPaddr2 \
                         ip=192.168.63.100 cidr_netmask=32 \
                         op monitor interval=30s

sudo pcs -f clust_cfg resource create mysql_VIP ocf:heartbeat:IPaddr2 \
                         ip=192.168.63.100 cidr_netmask=24 \
                         op monitor interval=300s

mysql_VIP は mysql_service の実行ノードで実行

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint colocation add mysql_VIP \
                         with mysql_service INFINITY

mysql_service の後に mysql_VIP を開始

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint order mysql_service then mysql_VIP
Adding mysql_service mysql_VIP (kind: Mandatory)
(Options: first-action=start then-action=start)

設定(制約)確認

[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg constraint
Location Constraints:
Ordering Constraints:
  promote MySQLClone then start mysql_fs (kind:Mandatory)
  start mysql_fs then start mysql_service (kind:Mandatory)
  start mysql_service then start mysql_VIP (kind:Mandatory)
Colocation Constraints:
  mysql_fs with MySQLClone (score:INFINITY) (with-rsc-role:Master)
  mysql_service with mysql_fs (score:INFINITY)
  mysql_VIP with mysql_service (score:INFINITY)
Ticket Constraints:
[centos7ha1.a5.jp]$ sudo pcs -f clust_cfg resource show
 Master/Slave Set: MySQLClone [mysql_data]
     Stopped: [ centos7ha1.a5.jp centos7ha2.a5.jp ]
 mysql_fs       (ocf::heartbeat:Filesystem):    Stopped
 mysql_service  (ocf::heartbeat:mysql): Stopped
 mysql_VIP      (ocf::heartbeat:IPaddr2):       Stopped

ここまで、cluster_cfg というfileに設定を入れていたが、 cib に push することで /var/lib/pacemaker/cib/cib.xml に保存

[centos7ha1.a5.jp]$ sudo pcs cluster cib-push clust_cfg
[ALL HOST]$ sudo pcs status
Cluster name: mycluster
Stack: corosync
Current DC: centos7ha1.a5.jp (version 1.1.19-8.el7_6.4-c3c624ea3d) - partition with quorum
Last updated: Sat Sep 14 21:07:24 2019
Last change: Sat Sep 14 21:05:47 2019 by root via cibadmin on centos7ha1.a5.jp

2 nodes configured
5 resources configured

Online: [ centos7ha1.a5.jp centos7ha2.a5.jp ]

Full list of resources:

 Master/Slave Set: MySQLClone [mysql_data]
     Masters: [ centos7ha1.a5.jp ]
     Slaves: [ centos7ha2.a5.jp ]
 mysql_fs       (ocf::heartbeat:Filesystem):    Started centos7ha1.a5.jp
 mysql_service  (ocf::heartbeat:mysql): Started centos7ha1.a5.jp
 mysql_VIP      (ocf::heartbeat:IPaddr2):       Started centos7ha1.a5.jp

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled

起動確認

[ALL HOST]$ sudo reboot

[centos7ha1.a5.jp]$ sudo pcs cluster start --all
centos7ha1.a5.jp: Starting Cluster (corosync)...
centos7ha2.a5.jp: Starting Cluster (corosync)...
centos7ha1.a5.jp: Starting Cluster (pacemaker)...
centos7ha2.a5.jp: Starting Cluster (pacemaker)...

## しばらくして、以下で確認
[ALL HOST]$ sudo pcs status
Cluster name: mycluster
Stack: corosync
Current DC: centos7ha1.a5.jp (version 1.1.19-8.el7_6.4-c3c624ea3d) - partition with quorum
Last updated: Sat Sep 14 21:16:45 2019
Last change: Sat Sep 14 21:05:47 2019 by root via cibadmin on centos7ha1.a5.jp

2 nodes configured
5 resources configured

Online: [ centos7ha1.a5.jp centos7ha2.a5.jp ]

Full list of resources:

 Master/Slave Set: MySQLClone [mysql_data]
     Masters: [ centos7ha1.a5.jp ]
     Slaves: [ centos7ha2.a5.jp ]
 mysql_fs       (ocf::heartbeat:Filesystem):    Started centos7ha1.a5.jp
 mysql_service  (ocf::heartbeat:mysql): Started centos7ha1.a5.jp
 mysql_VIP      (ocf::heartbeat:IPaddr2):       Started centos7ha1.a5.jp

Daemon Status:
  corosync: active/disabled
  pacemaker: active/disabled
  pcsd: active/enabled

mysqlの createdb や create table

[centos7ha1.a5.jp]$ mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Server version: 5.5.60-MariaDB MariaDB Server

[MariaDB]> CREATE DATABASE hatest CHARACTER SET utf8;
[MariaDB]> GRANT ALL PRIVILEGES ON hatest.* to 'hauser'@'localhost' IDENTIFIED BY 'hauser';
[MariaDB]> GRANT ALL PRIVILEGES ON hatest.* to 'hauser'@'%' IDENTIFIED BY 'hauser';

[MariaDB]> use hatest;
[MariaDB]> CREATE TABLE test_tbl (
             id              int primary key AUTO_INCREMENT,
             user_name       varchar(10) comment 'USER NAME')
             COMMENT='This is test table !!';
[MariaDB]> insert into test_tbl(user_name) values('tanaka'),('suzuki');
[MariaDB]> select * from test_tbl;
+----+-----------+
| id | user_name |
+----+-----------+
|  1 | tanaka    |
|  2 | suzuki    |
+----+-----------+

上記のcreate databaseやcreate tableを実行し、centos7ha1.a5.jp を停止。 ativeになった centos7ha2.a5.jp でレコードを追加し、 再度、centos7ha1.a5.jp をactiveに戻したところ問題なく centos7ha2.a5.jpで追加されたレコードも確認できました。

TODO

  • ざっと触ってみた程度ですので、きちんと?テストしないと怖いです
  • pcs(pacemaker)が持つコマンドを殆ど知らない為、調査(勉強?)が必要です
  • サーバ起動後「sudo pcs cluster start --all」を手動実行していますが、これをなくしたいです。
  • pcsでVIPを設定しましたが、以下のようなエラーの原因や対処方法、影響有無が未完了です。
$ sudo pcs status
Cluster name: mycluster
Stack: corosync
Current DC: centos7ha2.a5.jp (version 1.1.19-8.el7_6.4-c3c624ea3d) - partition with quorum
   :
Online: [ centos7ha1.a5.jp centos7ha2.a5.jp ]
   :
Full list of resources:
 Master/Slave Set: MySQLClone [mysql_data]
     Masters: [ centos7ha2.a5.jp ]
     Slaves: [ centos7ha1.a5.jp ]
 mysql_fs       (ocf::heartbeat:Filesystem):    Started centos7ha2.a5.jp
 mysql_service  (ocf::heartbeat:mysql):         Started centos7ha2.a5.jp
 mysql_VIP      (ocf::heartbeat:IPaddr2):       Stopped ←ココ!!!!!

↓ココ!!
Failed Actions:
* mysql_VIP_start_0 on centos7ha1.a5.jp 'unknown error' (1): call=30, status=complete, exitreason='[findif] failed',
    last-rc-change='Sun Sep 15 09:58:26 2019', queued=1ms, exec=144ms
* mysql_VIP_start_0 on centos7ha2.a5.jp 'unknown error' (1): call=90, status=complete, exitreason='[findif] failed',
    last-rc-change='Sun Sep 15 12:46:55 2019', queued=0ms, exec=52ms
  :

pcsで使用するコマンド例

$ sudo pcs status                               ## クラスタの状態確認

$ sudo pcs cluster start --all                  ## すべてのnodeを起動

$ sudo pcs cluster start centos7ha2.a5.jp       ## 特定ノードを起動
$ sudo pcs cluster stop  centos7ha2.a5.jp       ## 特定ノードを停止
$ sudo pcs resource move mysql_VIP centos7ha2.a5.jp ## 手動でfail over

$ sudo pcs resource restart mysql_VIP           ## pacemaker管理下のapacheの再起動

$ sudo pcs config                               ## pacemakerの設定の確認
$ sudo pcs resource list                        ## 使用可能なリソースの確認
$ sodo pcs resource standards                   ## クラスの確認

$ sudo pcs resource delete VIP                  # リソースの削除
                                                ## (クラスタがstartした状態で実行)

$ sudo corosync-cfgtool -s                      ## ノードの状態の確認
# 同じリソースの障害が何回発生するとフェイルオーバーするかを指定
$ sudo pcs resource defaults migration-threshold=1

linux (centos)で、disk, device, partision , dir (mountpoint)を一覧表示

ディスクの分割や、dirとの紐付けを知りたい場合、 df -l や、fdisk -l では分かりづらいですが、lsblk があります

$ lsblk
NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda               8:0    0   60G  0 disk 
├─sda1            8:1    0    1G  0 part /boot
└─sda2            8:2    0   59G  0 part 
  ├─centos-root 253:0    0   37G  0 lvm  /
  ├─centos-swap 253:1    0  3.9G  0 lvm  [SWAP]
  └─centos-home 253:2    0 18.1G  0 lvm  /home
sr0              11:0    1 1024M  0 rom  


$ lsblk -pi
NAME                        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
/dev/sda                      8:0    0   60G  0 disk 
|-/dev/sda1                   8:1    0    1G  0 part /boot
`-/dev/sda2                   8:2    0   59G  0 part 
  |-/dev/mapper/centos-root 253:0    0   37G  0 lvm  /
  |-/dev/mapper/centos-swap 253:1    0  3.9G  0 lvm  [SWAP]
  `-/dev/mapper/centos-home 253:2    0 18.1G  0 lvm  /home
/dev/sr0                     11:0    1 1024M  0 rom

OpenAMのlogin rest apiでlogin ticketを入手し、認証ページのhtmlをhttp get

2020/9/1 追記

このentry は、OpenAM ver.12 に対してのものでしたが、 その後、OpenAM ver.14 に対して行ったところ、 「'sso/json/sso/sessions'」のrequestで 501 not implemented というerrorが発生。

どうやら、APIの仕様が変更になっていた為、tomcat + openamの前段にある nginxの設定で、以下のように 「proxy_set_header Accept-API-Version 'resource=1.1, protocol=1.0'」を 加えることで解消。

location /sso/json/sso/sessions {
    proxy_pass http://127.0.0.1:28080/sso/json/sso/sessions;
    proxy_set_header Host $server_name;
    proxy_set_header Accept-API-Version 'resource=1.1, protocol=1.0'; ←追加★
    allow all;
}

以下のperl scriptの通り、LWP::UserAgent for perl , HTTP::Cookies for perl で実現できます

他参考 https://backstage.forgerock.com/docs/openam/13/dev-guide/

#!/usr/local/bin/perl
use utf8;
use strict;
use warnings;
use HTTP::Cookies;
use HTTP::Request;
use JSON;
use LWP::UserAgent;
use Data::Dumper;

my $SSO_HOST =                  'https://sso.hogehoge.com';
my $SSO_AUTHEN_API_PATH =       'sso/json/sso/authenticate';
my $SSO_VALIDATE_API_PATH =     'sso/json/sso/sessions';
my $PRIVATE_HOST =              'https://priv.hogehoge.com';
my $HTTP_TIMEOUT = 10;
my $LOCAL_COOKIE_FILE = "./cookie.txt";
my $COOKIE_DOMAIN = ".hogehoge.com";

my $UID_PW = {id=>"ないしょ",pw=>"ないしょ"};

main();


sub main {
    ## USER AGENT準備
    my $ua = LWP::UserAgent->new;
    $ua->timeout($HTTP_TIMEOUT);
    my $cookie_jar = HTTP::Cookies->new(file => $LOCAL_COOKIE_FILE,
                                        autosave => 1,
                                        ignore_discard => 1);
    $ua->cookie_jar($cookie_jar);

    ## SSOへlogin (login ticket取得)
    my $ret_auth = login_sso($ua);
    unless( $ret_auth ){
        die "fail login";
    }

    ## login ticketのvalidate (login idの取得)
    my $uid = validate_login($ua, $ret_auth);


    ## 認証要pageの取得
    get_private_page($ua,$uid,$ret_auth);
}

sub get_private_page {
    my ($ua,$uid,$ret_auth) = @_;
    
    my $cookie_jar = HTTP::Cookies->new(file => $LOCAL_COOKIE_FILE,
                                        autosave => 1,
                                        ignore_discard => 1);
    $cookie_jar->set_cookie(undef,              #version
                            "HTTP_uid",         #key
                            $uid,               #val
                            "/",                #path
                            $COOKIE_DOMAIN); #domain
    $cookie_jar->set_cookie(undef,                      #version
                            "iPlanetDirectoryPro",      #key
                            $ret_auth->{tokenId},       #val
                            "/",                        #path
                            $COOKIE_DOMAIN);         #domain
    $ua->cookie_jar($cookie_jar);
    
    my $top_url = join('/',$PRIVATE_HOST);
    my $req = HTTP::Request->new(GET => $top_url);

    my $res = $ua->request($req);
    if (not $res->is_success) {
        die $res->status_line;
    }

    # 認証要ページのHTMLが表示されます
    print $res->decoded_content;
}



sub validate_login {
    my ($ua, $ret_auth) = @_;

    my $url_validate =
        join('/',$SSO_HOST,$SSO_VALIDATE_API_PATH,$ret_auth->{tokenId});
    $url_validate .= '?_action=validate';
    my $req = HTTP::Request->new(POST => $url_validate);
    $req->header('Content-Type' => "application/json");

    my $res = $ua->request($req);
    if (not $res->is_success) {
        die $res->status_line;
    }

    my $ret_validate_json = $res->decoded_content;
    my $ret_validate = JSON::from_json($ret_validate_json);
    return $ret_validate->{uid};
}


sub login_sso {
    my ($ua) = @_;

    my $url = join('/',$SSO_HOST,$SSO_AUTHEN_API_PATH);
    my $req = HTTP::Request->new(POST => $url);
    $req->header('X-OpenAM-Username' => $UID_PW->{id});
    $req->header('X-OpenAM-Password' => $UID_PW->{pw});
    $req->header('Content-Type' => "application/json");

    my $res = $ua->request($req);
    if (not $res->is_success) {
        die $res->status_line;
    }

    my $ret_auth_json = $res->decoded_content;
    my $ret_auth = JSON::from_json($ret_auth_json);
    return $ret_auth;
}

git-2.22.1 make で git-imap-send Error

原因不明...時間もないので、yum install にしよ

$ wget https://github.com/git/git/archive/v2.22.1.tar.gz
$ cd git-2.22.1/
$ make configure
$ ./configure --prefix=/usr/local
$ make all doc info
  :
LINK git-imap-send
http.o: In function `http_init':
/home/end0tknr/tmp/git-2.22.1/http.c:1083: undefined reference to `curl_global_sslset'
collect2: error: ld returned 1 exit status
make: *** [git-imap-send] Error 1

(javascript) node.js + express による mysql への接続

先日までに vue.js に触れてみましたが、 サーバ側の処理も javascript(node.js)で試したくなりました。 (そうすれば、クライアント側もサーバ側も javascriptで統一できますからね)

そこで、node.js + express による mysql への接続を実装してみました。以下

試した感想

簡単な実装はできますが、javaやperl、pythonと比較すると、
node.jsはmoduleや日本語情報が、まだまだ少ないので、
現時点では、サーバ側の実装は、これまで通り、javaやperl、python を利用した方が良さそう。

という感じ

下準備

## ↓ express-generator とは spring boot のようなものと理解してます
$ sudo /usr/bin/npm install --global express-generator

## 上記により、/usr/bin/express が利用できますので
## projectを template engine=ejsで作成
$ /usr/bin/express --view=ejs node_exp_gen_mysql
  create : node_exp_gen_mysql/
     <略>
   create : node_exp_gen_mysql/public/javascripts/
   create : node_exp_gen_mysql/bin/www

   change directory:
     $ cd node_exp_gen_mysql
   install dependencies:
     $ npm install
   run the app:
     $ DEBUG=node-exp-gen-mysql:* npm start

## 上記により、 package.json が作成されていますので
## npm install により、package.json 内にある依存moduleをinstall
$ cd node_exp_gen_mysql
$ npm install
node-exp-gen-mysql@0.0.0 /home/end0tknr/tmp/node_exp_gen_mysql
├─┬ cookie-parser@1.4.4 
:  :  
├── ejs@2.6.2 
├─┬ express@4.16.4 
│  ├─┬ accepts@1.3.7 
:  :  :


## 試しにnpm startにより起動し、ブラウザでアクセス( http://cent76.a5.jp:3000/ )
## start というコマンドは package.json 内に定義されています。
$ npm start

## ctrl+cでサーバ停止
$ ^C

## mysql moduleのinstall

$ /home/end0tknr/tmp/node_exp_gen_mysql
$ npm install --save mysql
node-exp-gen-mysql@0.0.0 /home/end0tknr/tmp/node_exp_gen_mysql
└─┬ mysql@2.17.1 
    ├── bignumber.js@7.2.1 
    ├─┬ readable-stream@2.3.6 
    │  ├── core-util-is@1.0.2 
    │  ├── isarray@1.0.0 
    │  ├── process-nextick-args@2.0.1 
    │  ├── string_decoder@1.1.1 
    │  └── util-deprecate@1.0.2 
    └── sqlstring@2.3.1 

実装したsrc (defaultからの変更分のみ)

routes/index.js

var express = require('express');
var router = express.Router();

var mysql      = require('mysql');
var dbh = mysql.createConnection({
    host     : 'localhost',
    user     : 'root',
    password : '',
    database : 'test_db'
});

/* GET home page. */
router.get('/', function(req, res, next) {

    dbh.connect();
    var sql =
    'select tei_name,build_address from anken where tei_name like ? limit 5';

    var render_data = { title: 'nodejs express mysql test',
            db_results: []};

    dbh.query(sql, ['%鈴木%'], function (error, results, fields) {

    if (error) throw error;
    render_data['db_results'] = results;
    console.log(results);
    
    res.render('index', render_data);
    });

    dbh.end();
});

module.exports = router;

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>

    <table>
      <tbody>
      <% for(var i in db_results) { %>
      <tr>
        <% var obj = db_results[i]; %>
        <th><%= obj.tei_name %></th>
        <td><%= obj.build_address %></td>
      </tr>
      <% } %>
      </tbody>
    </table>
    <p>Welcome to <%= title %></p>
  </body>
</html>

install mongodb ver.4.2.0 from src

gcc ver.8.3.0の準備もできたので、 mongodb に付属の docs/building.md と https://shoken.hatenablog.com/entry/2015/07/13/162548 を参考に実施。

download & build & install

https://www.mongodb.com/download-center/community

$ wget https://fastdl.mongodb.org/src/mongodb-src-r4.2.0.tar.gz
$ tar -xvf mongodb-src-r4.2.0.tar.gz
$ cd mongodb-src-r4.2.0
$ less docs/building.md

$ sudo /usr/local/python3/bin/pip3 install -r etc/pip/compile-requirements.txt

# ↓私の環境では3時間程、かかりました
$ /usr/local/python3/bin/python3 buildscripts/scons.py core

$ sudo /usr/local/python3/bin/python3 buildscripts/scons.py \
  --prefix=/usr/local/mongodb install

設定

$ wget https://raw.githubusercontent.com/mongodb/mongo/master/rpm/mongod.conf
$ vi mongod.conf
  old)  dbPath: /var/lib/mongo
  new)  dbPath: /home/mongod/data

$ sudo mv mongod.conf /etc/mongod.conf

$ sudo mkdir /var/log/mongodb
$ sudo mkdir /var/run/mongodb
$ sudo mkdir /home/mongo/data

で、試しに rootで起動 & 接続.

(色々とwarningが表示されますが、最後に「>」が表示され起動されたことが確認できます)

(この起動により root権限で /home/mongod/data 以下に様々なfileが作成されますので mongod 停止後、sudo rm -rf /home/mongod/data/* します)

起動
$ sudo /usr/local/mongodb/bin/mongod -f /etc/mongod.conf

接続確認
$ /usr/local/mongodb/bin/mongo
MongoDB shell version v4.2.0
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("3a4e52bf-3401-4324-be73-22efc394d473") }
MongoDB server version: 4.2.0
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
    http://docs.mongodb.org/
Questions? Try the support group
    http://groups.google.com/group/mongodb-user
  :
2019-08-14T03:12:55.595+0900 I  CONTROL  [initandlisten] **        We suggest setting it to 'never'
2019-08-14T03:12:55.595+0900 I  CONTROL  [initandlisten] 
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---

> 

停止
$ sudo kill `cat /var/run/mongodb/mongod.pid`
$ sudo rm /var/lib/mongo/mongod.lock

掃除
$ sudo rm -rf /home/mongod/data/*

自動起動設定

$ sudo /usr/sbin/groupadd -r mongod
$ sudo /usr/sbin/useradd -r -g mongod mongod

$ sudo chown -R mongod:mongod /usr/local/mongodb
$ sudo chown -R mongod:mongod /var/log/mongodb
$ sudo chown -R mongod:mongod /var/run/mongodb

$ wget https://raw.githubusercontent.com/mongodb/mongo/master/rpm/mongod.service
$ vi mongod.service
 old) ExecStart=/usr/bin/mongod $OPTIONS
 new) ExecStart=/usr/local/mongodb/bin/mongod $OPTIONS

$ sudo mv mongod.service /usr/lib/systemd/system/
$ cd /usr/lib/systemd/system/
$ sudo systemctl enable mongod
$ sudo systemctl start mongod.service
$ sudo systemctl status mongod.service

install gcc ver.8.3.0 from source to centos 7

mongo db を srcから installしようとしましたが、centos 7付属のgccが古かった為、 gcc ver.8.3.0 をsrcから install

$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-8.3.0/gcc-8.3.0.tar.gz
$ tar -xvf gcc-8.3.0.tar.gz
$ cd gcc-8.3.0cd
# 以下にて必要なライブラリ?がダウンロードされます
$ ./contrib/download_prerequisites 

$ mkdir build
$ cd build
$ ../configure \
    --prefix=/usr/local/gcc8 \
    --disable-bootstrap \
    --disable-multilib \
    --enable-languages=c,c++
$ make
$ sudo make install

g++: internal compiler error: Killed (program cc1plus) のエラー

上記の configureでは、いくつかoptionを指定していますが、 このoptionなしで、makeを実行したところ、以下のようなエラーとなりました。

g++: internal compiler error: Killed (program cc1plus)

インターネットで検索しましたが、メモリ不足が原因のようでしたので 上記のoption指定や、不要なdaemonを停止、更には make -jでなく単なる「make」によりgccのbuildができました

既存のgccをmvでバックアップ後、ln -s で配備

今回の gcc ver.8.30は /usr/local/gcc8 へ installし /usr/bin/gcc や /usr/bin/g++ 、/lib64/libstdc++.so.6 はmvでバックアップし ln -s で置き換え?しています。

$ sudo mv /usr/bin/gcc /usr/bin/gcc.20190810
$ sudo mv /usr/bin/g++ /usr/bin/g++.20190810
$ sudo ln -s /usr/local/gcc8/bin/gcc /usr/bin/gcc
$ sudo ln -s /usr/local/gcc8/bin/g++ /usr/bin/g++

$ sudo mv /lib64/libstdc++.so.6 /lib64/libstdc++.so.6.20190810
$ sudo ln -s /usr/local/gcc8/lib64/libstdc++.so.6.0.25 libstdc++.so.6

ちなみに libstdc++.so.6 の ln -s を行う前は、mongod 起動時に 次のエラーが表示されていました。

$ sudo /usr/local/mongodb/bin/mongod -f /etc/mongod.conf
/usr/local/mongodb/bin/mongod: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by /usr/local/mongodb/bin/mongod)
/usr/local/mongodb/bin/mongod: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.22' not found (required by /usr/local/mongodb/bin/mongod)
/usr/local/mongodb/bin/mongod: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by /usr/local/mongodb/bin/mongod)
/usr/local/mongodb/bin/mongod: /lib64/libstdc++.so.6: version `CXXABI_1.3.8' not found (required by /usr/local/mongodb/bin/mongod)

また、 ln -s libstdc++.so.6 後、GLIBCXX_3.4.20 等が含まれることの確認は 次のように strings + grep で行っています。

$ strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

Vue.js の練習

https://jp.vuejs.org/

先程のエントリでは Reactでしたが、Vue.jsも触れたことがない為、dotinstall.com にて写経。

ReactはJSXで実装しますが、Vue.jsはjavascriptで実装できる為、 こちらの方が入門しやすい印象です。

以下は、写経した 2つのサンプルで、詳細はsrcを読めば分かります。

写経 その1 - TODO管理

html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Vue.jsの練習</title>
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>

  <div id="app" class="container">
    <h1>
      <button @click="purge">完了TODOを一括削除</button>
      TODO一覧
      <span class="info">({{ remaining.length }}/{{ todos.length }})</span>
    </h1>
    <ul>
      <li v-for="(todo, index) in todos">
        <input type="checkbox" v-model="todo.isDone">
        <span :class="{done: todo.isDone}">{{ todo.title }}</span>
        <span @click="deleteItem(index)" class="command">[x]</span>
      </li>
      <li v-show="!todos.length">残っているTODOはありません!</li>
    </ul>
    <form @submit.prevent="addItem">
      <input type="text" v-model="newItem">
      <input type="submit" value="追加">
    </form>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="js/main.js"></script>
</body>
</html>

javascript

(function() {
    'use strict';
    
    var vm = new Vue({
    el: '#app', // vueを実体化するelementの指定
    data: {
        newItem: '',
        todos: []
    },
    computed: {
        remaining: function() {
        return this.todos.filter(function(todo) {
            return !todo.isDone;
        });
        }
    },
    watch: {
        todos: { // todoに変更があった場合、localStorageに保存
        handler: function() {
            localStorage.setItem('todos', JSON.stringify(this.todos));
        },
        deep: true // hash内部をdeepにwatch
        }
    },
    // 起動直後に localStorage から load
    mounted: function() {
        this.todos = JSON.parse(localStorage.getItem('todos')) || [];
    },
    methods: {
        addItem: function() {
        var item = {
            title: this.newItem,
            isDone: false
        };
        this.todos.push(item);
        this.newItem = '';
        },
        deleteItem: function(index) {
        if (confirm('削除してよろしいですか?')) {
            this.todos.splice(index, 1);
        }
        },
        purge: function() {
        if (!confirm('削除してよろしいですか?')) {
            return;
        }
        this.todos = this.remaining;
        }
    }
    });
})();

css

body {}

.container {
  width: 400px;
  margin: auto;
}

#app h1 {
  font-size: 20px;
  border-bottom: 1px solid #888;
}

#app li {}

#app input[type="text"] {}

.command {
  font-size: 12px;
  cursor: pointer;
  color: #08c;
}

#app ul {
  padding: 0;
  list-style: none;
}

#app li > span.done {
  text-decoration: line-through;
}

.info {
  color: #888;
  font-weight: normal;
}

#app h1 > button {
  float: right;
}

写経 その2 - ボタンクリック カウンタ

html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Vue.jsの練習 2</title>
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>

  <div id="app">
    <p>総合計: {{ total }}</p>
    <like-component message="Like"    @increment="incrementTotal"></like-component>
    <like-component message="Awesome" @increment="incrementTotal"></like-component>
    <like-component message="Great"   @increment="incrementTotal"></like-component>
    <like-component @increment="incrementTotal"></like-component>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="js/main.js"></script>
</body>
</html>

javascript

(function() {
    'use strict';

    var likeComponent = Vue.extend({
    props: {
        message: {
        type: String,
        default: 'Like'
        }
    },
    data: function() {
        return {
        count: 0
        }
    },
    template: '<button @click="countUp">{{ message }} {{ count }}</button>',
    methods: {
        countUp: function() {
        this.count++;
        this.$emit('increment'); // eventの発行
        }
    }
    });
    
    var app = new Vue({
    el: '#app',
    components: {
        'like-component': likeComponent
    },
    data: {
        total: 0
    },
    methods: {
        incrementTotal: function() {
        this.total++;
        }
    }
    });
})();

css

body {}

React js の練習 (javascript?)

https://reactjs.org/

とりあえず react に触れてみたかった。

ググってみると、nodejs のinstallから紹介しているサイトを多く見かけますが、 ブラウザ + エディタだけで気軽に試すことができる dotinstall.com にて写経。

以下は、その成果物のTODO管理。読めば、分かると思います

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>reactjs.org の練習 at dotinstall.com </title>
  <link rel="stylesheet" href="css/styles.css">
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>

  <!--  #root 以下を Reactが制御/描画します -->
  <div id="root"></div>
  
  <script type="text/babel">
   (() => {
     function TodoHeader(props) {
       // filter()で未完了のTODOを抽出
       const remaining = props.todos.filter(todo => {
         return !todo.isDone;
       });
       
       return (
         <h1>
         My Todos
         <span>({remaining.length}/{props.todos.length})</span>
    
         <button onClick={props.purge}>完了TODOの一括消去</button>
    </h1>
       );
     }
     
     function TodoList(props) {
       // map() で、TODOを <TodoItem / > Component に変換
       const todos = props.todos.map(todo => {
         return (
           <TodoItem
           key={todo.id}
           todo={todo}
           checkTodo={props.checkTodo}
           deleteTodo={props.deleteTodo}
           />
         );
       });
       return (
         <ul>
         {props.todos.length ? todos : <li>対応すべきTODOはありません!</li>}
         </ul>
       );
     }


     function TodoItem(props) {
       return (
         <li key={props.todo.id}>
         <label>
         <input type="checkbox"
         checked={props.todo.isDone}
         onChange={() => props.checkTodo(props.todo)}
         />
         <span className={props.todo.isDone ? 'done' : ''}>
         {props.todo.title}
         </span>
         </label>
         <span className="cmd" onClick={() => props.deleteTodo(props.todo)}>[×]</span>
         </li>
       );
     }
     

     function TodoForm(props) {
       return (
         <form onSubmit={props.addTodo}>
         <input type="text" value={props.item} onChange={props.updateItem}/>
         <input type="submit" value="追加"/>
         </form>
       );
     }

     function getUniqueId() {
       return new Date().getTime().toString(36) + '-' + Math.random().toString(36);
     }
     
     class App extends React.Component {
       constructor() {
         super();
    // Reactの作法的に、stateは1箇所のcomponentで管理するらしい
         this.state = {
           todos: [],
           item: ''   // 新たに追加するTODOの内容
         };
    
         this.checkTodo =  this.checkTodo.bind(this);
         this.deleteTodo = this.deleteTodo.bind(this);
         this.updateItem = this.updateItem.bind(this);
         this.addTodo =    this.addTodo.bind(this);
         this.purge =      this.purge.bind(this);
       }
       
       purge() {
         if ( ! confirm('完了TODOを一括消去してよろしいですか ?')) {
           return;
         }
    // filter() で未完了TODOを集め、 setState() にて更新
         const todos = this.state.todos.filter(todo => {
           return ! todo.isDone;
         });
         this.setState({
           todos: todos
         });
       }
       
       addTodo(e) {
    // onSubmit()で呼ばれる為、画面遷移されることを防ぎます
         e.preventDefault();
    
         if (this.state.item.trim() === '') {
           return;
         }
    
         const item = {
           id: getUniqueId(),
           title: this.state.item,
           isDone: false
         };

    // slice()にて、元の配列をshallow copy
         const todos = this.state.todos.slice();
         todos.push(item);
         this.setState({
           todos: todos,
           item: ''
         });
       }

       deleteTodo(todo) {
         if (!confirm('削除してよろしいですか ?')) {
           return;
         }
    
    // slice()にて、元の配列をshallow copyし
    // indexOf()にて削除対象の位置を探索
         const todos = this.state.todos.slice();
         const pos = this.state.todos.indexOf(todo);

    // splice()にて、該当のTODOを削除
          todos.splice(pos, 1);
          this.setState({
           todos: todos
         });
       }

       checkTodo(todo) {
         const todos = this.state.todos.map(todo => {
           return {id: todo.id, title: todo.title, isDone: todo.isDone};
         });
    
         const pos = this.state.todos.map(todo => {
           return todo.id;
         }).indexOf(todo.id);

         todos[pos].isDone = !todos[pos].isDone;
         this.setState({
           todos: todos
         });
       }
       
       // TodoForm componentの onChange()から呼ばれます
       updateItem(e) {
         this.setState({
           item: e.target.value
         });
       }

       // componentDidUpdate()は、Component の props や state が
       // 更新された際に呼ばれます.
       // ここでは、TODOの内容をjsonにして、localStorage へ保存しています
       componentDidUpdate() {
         localStorage.setItem('todos', JSON.stringify(this.state.todos));
       }
       // componentDidMount()により、上記で json保存した内容を画面にloadしています
       componentDidMount() {
         this.setState({
           todos: JSON.parse(localStorage.getItem('todos')) || []
         });
       }
       
        render() {
          return (
            <div className="container">
              <TodoHeader
                todos={this.state.todos}
                purge={this.purge}
              />
              <TodoList
                todos={this.state.todos}
                checkTodo={this.checkTodo}
                deleteTodo={this.deleteTodo}
              />
              <TodoForm
                item={this.state.item}
                updateItem={this.updateItem}
                addTodo={this.addTodo}
              />
            </div>
          );
        }
      }

      ReactDOM.render(
        <App/>,
        document.getElementById('root')
      );
    })();
  </script>
</body>
</html>
body {
}

.container {
  width: 400px;
  margin: auto;
}

.container h1 {
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}

.container ul {
  padding: 0;
  list-style: none;
}

.container li {
  line-height: 2;
}

.container input[type="checkbox"] {
  margin-right: 5px;
}

.cmd {
  cursor: pointer;
  color: #08c;
  margin-left: 5px;
}

.container input[type="text"] {
  padding: 2px;
  margin-right: 5px;
}

h1 > span {
  color: #ccc;
  font-weight: normal;
  margin-left: 5px;
}


h1 > button {
  float: right;
}

.done {
  text-decoration: line-through;
  color: #ccc;
}