LoginSignup
5
5

More than 5 years have passed since last update.

CentOS6 で Zabbix 3.0 を動かす (力業編)

Last updated at Posted at 2016-03-04

力技で動かす

CentOS6 で Zabbix 3.0 を動かす実験の記録です。MySQL 用を使って実験しました。

検証は CentOS 6.7 で行いました。

rpm パッケージと、対応する glibc や libmysqlclient、libcurl をほどいて配置して動かす方法です。ソースからのビルドは不要ですが、必要となる glibc のバージョンが合わないため、かなり強引な方法で実現しています。

CentOS6 で Zabbix 3.0 を動かす (自力ビルド編) も参照してください。

パッケージを入手する

リポジトリから直接パッケージをダウンロードしました。

zabbix-agent-3.0.1-1.el7.x86_64.rpm
zabbix-get-3.0.1-1.el7.x86_64.rpm
zabbix-java-gateway-3.0.1-1.el7.x86_64.rpm
zabbix-proxy-mysql-3.0.1-1.el7.x86_64.rpm
zabbix-sender-3.0.1-1.el7.x86_64.rpm
zabbix-server-mysql-3.0.1-1.el7.x86_64.rpm
zabbix-web-3.0.1-1.el7.noarch.rpm
zabbix-web-japanese-3.0.1-1.el7.noarch.rpm
zabbix-web-mysql-3.0.1-1.el7.noarch.rpm

今回は実験のために一通りダウンロードしましたが、zabbix-server-mysql と zabbix-web の一連のパッケージ以外は RHEL6/CentOS6 用のものが提供されています。

MySQL

remi リポジトリのパッケージを導入しました。
インストールしたパッケージは以下の通りです。

  • mysql-libs-5.5.48-1.el6.remi.x86_64
  • compat-mysql51-5.1.54-1.el6.remi.x86_64
  • mysql-5.5.48-1.el6.remi.x86_64
  • perl-DBD-MySQL-4.013-3.el6.x86_64
  • mysql-server-5.5.48-1.el6.remi.x86_64

パッケージをほどいて配置

rpm パッケージと、対応する glibc や libmysqlclient、libcurl を解いて配置します。
配置先を /opt/zabbix とします。

# mkdir /opt/zabbix
# rpm2cpio zabbix-server-mysql-3.0.1-1.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)
# rpm2cpio zabbix-agent-3.0.1-1.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)
# rpm2cpio zabbix-get-3.0.1-1.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)
# rpm2cpio zabbix-sender-3.0.1-1.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)

データベースの作成

データベースと接続ユーザを作成します。以下は、ユーザ名 zabbix パスワード zabbix での例です。

$ mysql -uroot -p
Enter password:
mysql> create database zabbix character set utf8 collate utf8_bin;
mysql> grant all privileges on zabbix.* to zabbix@localhost identified by 'zabbix';
mysql> quit;
$ gzip -dc /opt/zabbix/usr/share/doc/zabbix-server-mysql-3.0.1/create.sql.gz |
mysql -uzabbix -pzabbix zabbix 

設定ファイル

ディレクトリのシンボリックリンクを張っておきます。

# ln -s /opt/zabbix/etc/zabbix /etc/zabbix
# ln -s /opt/usr/lib/zabbix/alertscripts /usr/lib/zabbix/alertscripts
# ln -s /opt/usr/lib/zabbix/externalscripts /usr/lib/zabbix/externalscripts

設定ファイルを修正します。

zabbix_server.conf.patch
diff -u zabbix_server.conf zabbix_server.conf
--- zabbix_server.conf- 2016-02-28 16:23:55.000000000 +0900
+++ zabbix_server.conf  2016-03-02 15:50:50.000000000 +0900
@@ -114,6 +114,8 @@
 # Default:
 # DBPassword=

+DBPassword=zabbix
+
 ### Option: DBSocket
 #      Path to MySQL socket.
 #
@@ -121,6 +123,8 @@
 # Default:
 # DBSocket=/tmp/mysql.sock

+DBSocket=/var/lib/mysql/mysql.sock
+
 ### Option: DBPort
 #      Database port when not using local socket. Ignored for SQLite.
 #
# chown zabbix /etc/zabbix/zabbix_server.conf
# chmod 640 /etc/zabbix/zabbix_server.conf

共有ライブラリ

共有ライブラリが足りているかどうかチェックします。

$ ldd /opt/zabbix/usr/sbin/zabbix_server_mysql
/opt/zabbix/usr/sbin/zabbix_server_mysql: /lib64/libc.so.6: version `GLIBC_2.15' not found (required by /opt/zabbix/usr/sbin/zabbix_server_mysql)
/opt/zabbix/usr/sbin/zabbix_server_mysql: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by /opt/zabbix/usr/sbin/zabbix_server_mysql)
/opt/zabbix/usr/sbin/zabbix_server_mysql: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by /opt/zabbix/usr/sbin/zabbix_server_mysql)
        linux-vdso.so.1 =>  (0x00007ffc81169000)
        libmysqlclient.so.18 => /usr/lib64/mysql/libmysqlclient.so.18 (0x00007fb896437000)
        libiksemel.so.3 => /usr/lib64/libiksemel.so.3 (0x00007fb896229000)
        libxml2.so.2 => /usr/lib64/libxml2.so.2 (0x00007fb895ed6000)
        libodbc.so.2 => /usr/lib64/libodbc.so.2 (0x00007fb895c6f000)
        libnetsnmp.so.31 => not found
        libssh2.so.1 => /usr/lib64/libssh2.so.1 (0x00007fb895a46000)
        libOpenIPMI.so.0 => /usr/lib64/libOpenIPMI.so.0 (0x00007fb89573e000)
        libOpenIPMIposix.so.0 => /usr/lib64/libOpenIPMIposix.so.0 (0x00007fb895539000)
        libssl.so.10 => /usr/lib64/libssl.so.10 (0x00007fb8952cc000)
        libcrypto.so.10 => /usr/lib64/libcrypto.so.10 (0x00007fb894ee8000)
        libldap-2.4.so.2 => /lib64/libldap-2.4.so.2 (0x00007fb894c98000)
        liblber-2.4.so.2 => /lib64/liblber-2.4.so.2 (0x00007fb894a88000)
        libcurl.so.4 => /usr/lib64/libcurl.so.4 (0x00007fb894822000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fb89459e000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fb894399000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fb89417f000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fb893deb000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb893bcd000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fb8939b7000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fb8937af000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fb8934a8000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fb893292000)
        libgnutls.so.26 => /usr/lib64/libgnutls.so.26 (0x00007fb892fef000)
        libgcrypt.so.11 => /lib64/libgcrypt.so.11 (0x00007fb892d79000)
        libgpg-error.so.0 => /lib64/libgpg-error.so.0 (0x00007fb892b75000)
        libltdl.so.7 => /usr/lib64/libltdl.so.7 (0x00007fb89296c000)
        libOpenIPMIutils.so.0 => /usr/lib64/libOpenIPMIutils.so.0 (0x00007fb892763000)
        libncurses.so.5 => /lib64/libncurses.so.5 (0x00007fb892541000)
        libgdbm.so.2 => /usr/lib64/libgdbm.so.2 (0x00007fb89233b000)
        libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fb8920f6000)
        libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fb891e0f000)
        libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fb891c0b000)
        libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fb8919de000)
        libsasl2.so.2 => /usr/lib64/libsasl2.so.2 (0x00007fb8917c4000)
        libssl3.so => /usr/lib64/libssl3.so (0x00007fb891584000)
        libsmime3.so => /usr/lib64/libsmime3.so (0x00007fb891357000)
        libnss3.so => /usr/lib64/libnss3.so (0x00007fb891018000)
        libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007fb890dec000)
        libplds4.so => /lib64/libplds4.so (0x00007fb890be7000)
        libplc4.so => /lib64/libplc4.so (0x00007fb8909e2000)
        libnspr4.so => /lib64/libnspr4.so (0x00007fb8907a4000)
        libidn.so.11 => /lib64/libidn.so.11 (0x00007fb890571000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb89691f000)
        libtasn1.so.3 => /usr/lib64/libtasn1.so.3 (0x00007fb890361000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fb89013f000)
        libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fb88ff34000)
        libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fb88fd31000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fb88faf9000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb88f8da000)
        libfreebl3.so => /lib64/libfreebl3.so (0x00007fb88f6d6000)

RHEL7/CentOS7 用にビルドされた Zabbix 3.0 は glibc 2.17 を要求します。
また libnetsnmp.so.31 が not found になっています。

CentOS7 のリポジトリからパッケージをダウンロードしてきます。
また、curl も CentOS6 ものは、バージョンが古く SMTP AUTH が使えないためこちらもダウンロードしてきます。

先ほどと同様に展開します。

# rpm2cpio glibc-2.17-105.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)
# net-snmp-libs-5.7.2-24.el7.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)
# rpm2cpio libcurl-7.29.0-25.el7.centos.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)

ひとまず実行してみる

起動できるかどうか試してみます。

$ LD_LIBRARY_PATH=/opt/zabbix/lib64:/opt/zabbix/usr/lib64 /opt/zabbix/usr/sbin/zabbix_server_mysql -h
/opt/zabbix/usr/sbin/zabbix_server_mysql: relocation error: /opt/zabbix/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference

起動時に使用するダイナミックリンカー ld-linux-x86-64.so.2 が合っていないようです。

CentOS6 と CentOS7 では、glibc のバージョンが異なります。
CentOS6 の glibc は 2.12 CentOS7 の glibc は 2.17 となっています。

このため、rpm パッケージと、対応する glibc や libmysqlclient、libcurl を解いて配置して、環境変数 LD_LIBRARY_PATH を設定しただけではうまく起動できません。

glibc の場合、libc-2.XX.so とダイナミックリンカー ld-2.XX.so とが対応している必要があるのですが、起動時に使用されるダイナミックリンカーは、ロード-モジュールを作成するときに、libc.so に記載されているものがプログラムヘッダの INTERP に埋め込まれます。

/usr/lib64/libc.so
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a  AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) )
$ readelf  -l -S   /opt/zabbix/usr/sbin/zabbix_server_mysql  | sed -n '/Program Headers:/,/program interpreter/p'
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

/lib64/ld-linux-x86-64.so.2 は CentOS6 では、2.12 用のものなのでうまく起動できません。

ダイナミックリンカーには、プログラムが起動されたときに、必要な共有ライブラリを読み込むヘルパープログラムの機能のほかに、ロードモジュールを起動する機能があります。

NAME
       ld.so, ld-linux.so* - dynamic linker/loader

SYNOPSIS
       The dynamic linker can be run either indirectly by running some dynami‐
       cally linked program or library (in which case no command-line  options
       to  the  dynamic linker can be passed and, in the ELF case, the dynamic
       linker which is stored in the .interp section of the  program  is  exe‐
       cuted) or directly by running:

       /lib/ld-linux.so.*  [OPTIONS] [PROGRAM [ARGUMENTS]]

OPTIONS

       --library-path PATH
              Use PATH instead of LD_LIBRARY_PATH environment variable setting
              (see below).

ダイナミックリンカ ld-linux-x86-64.so.2 に、共有ライブラリの検索パスを指定して起動し、ロードモジュールを呼び出すようにすれば問題なく動作します。

やってみます。

$ /opt/zabbix/lib64/ld-linux-x86-64.so.2 --library-path /opt/zabbix/lib64:/opt/zabbix/usr/lib64 /opt/zabbix/usr/sbin/zabbix_server_mysql -h
usage:
  zabbix_server_mysql [-c config-file]
  zabbix_server_mysql [-c config-file] -R runtime-option
  zabbix_server_mysql -h
  zabbix_server_mysql -V

The core daemon of Zabbix software.

Options:
  -c --config config-file        Absolute path to the configuration file
                                 (default: "/etc/zabbix/zabbix_server.conf")
  -f --foreground                Run Zabbix server in foreground
  -R --runtime-control runtime-option   Perform administrative functions

    Runtime control options:
      config_cache_reload        Reload configuration cache
      housekeeper_execute        Execute the housekeeper
      log_level_increase=target  Increase log level, affects all processes if
                                 target is not specified
      log_level_decrease=target  Decrease log level, affects all processes if
                                 target is not specified

      Log level control targets:
        pid                      Process identifier
        process-type             All processes of specified type (e.g., poller)
        process-type,N           Process type and number (e.g., poller,3)

  -h --help                      Display this help message
  -V --version                   Display version number

Report bugs to: <https://support.zabbix.com>
Zabbix home page: <http://www.zabbix.com>
Documentation: <https://www.zabbix.com/documentation>

だいじょうぶなようです。

註) この実行結果だけではわかりませんが、じつは libmysqlclient.so.18 は CentOS7 のリポジトリにあるMariaDB のものを使用しないと実行時に未定義シンボルエラーが出ます。このことについては後ほど触れます。

スタートアップスクリプト

CentOS7 では systemd を使用する形になっているため、旧来のスタートアップスクリプトはパッケージに含まれていません。

そこで Zabbix 2.4 のパッケージ含まれるものを使用するため、アーカイブから展開します。

# rpm2cpio zabbix-server-2.4.7-1.el6.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu ./etc/init.d/zabbix-server)
# rpm2cpio zabbix-agent-2.4.7-1.el6.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu
./etc/init.d/zabbix-agent)

ld-linux-x86-64.so.2 経由で起動するように修正します。

zabbix-server.init.patch
--- /opt/zabbix/etc/init.d/zabbix-server        2015-11-13 19:14:20.000000000 +0900
+++ /opt/zabbix/etc/init.d/zabbix-server        2016-03-03 23:57:02.975726383 +0900
@@ -18,11 +18,19 @@
 # Source function library.
 . /etc/rc.d/init.d/functions

-if [ -x /usr/sbin/zabbix_server ]; then
-    exec=zabbix_server
+zroot=/opt/zabbix
+if [ -x $zroot/usr/sbin/zabbix_server ]; then
+    #exec=$zroot/usr/sbin/zabbix_server
+    exec=$zroot/usr/sbin/zabbix_server_mysql
 else
     exit 5
 fi
+if [ -x $zroot/lib64/ld-linux-x86-64.so.2 ]; then
+    ld_so=$zroot/lib64/ld-linux-x86-64.so.2
+else
+    exit 5
+fi
+library_path=$zroot/lib64:$zroot/usr/lib64

 prog=${exec##*/}
 conf=/etc/zabbix/zabbix_server.conf
@@ -38,7 +46,7 @@
 start()
 {
     echo -n $"Starting Zabbix server: "
-    daemon $exec -c $conf
+    LD_LIBRARY_PATH=$library_path $ld_so $exec -c $conf
     rv=$?
     echo
     [ $rv -eq 0 ] && touch $lockfile

実行ユーザ zabbix と実行グループ zabbix を作成しておきます。

# groupadd -g 119 zabbix
# useradd -u 119 -g 119 -d /var/run/zabbix/ -s /bin/false zabbix 

ログディレクトリを作成しておきます。

install -o zabbix -g zabbix -d /var/log/zabbix

起動してみます。

# sh -x /opt/zabbix/etc/init.d/zabbix-server start

実行してみると一見エラーは出ないものの、起動したプロセスがありません。zabbix_server.log を見ると未解決シンボルのエラーが出ていました。

/var/log/zabbix/zabbix_server.log
/opt/zabbix/usr/sbin/zabbix_server_mysql: relocation error: /opt/zabbix/usr/sbin/zabbix_server_mysql: symbol mysql_init, version libmysqlclient_18 not defined in file libmysqlclient.so.18 with link time reference

CentOS7 では MariaDB を使用するようになっているため、libmysqlclient.so.18 は CentOS7 のリポジトリにある MariaDB のものを使用しないとダメなようです。CentOS7 のリポジトリからパッケージをダウンロードしてきて展開します。

# rpm2cpio mariadb-libs-5.5.44-2.el7.centos.x86_64.rpm | (cd /opt/zabbix && cpio -idmvu)

スタートアップスクリプトの library_path に追加します。

zabbix-server.init.2.patch
--- zabbix-server       2016-03-04 02:28:56.955721309 +0900
+++ zabbix-server       2016-03-03 23:57:02.975726383 +0900
@@ -30,7 +30,7 @@
 else
     exit 5
 fi
-library_path=$zroot/lib64:$zroot/usr/lib64
+library_path=$zroot/lib64:$zroot/usr/lib64:$zroot/usr/lib64/mysql

 if [ ! -d /var/log/zabbix ]; then
     install -o zabbix -g zabbix -d /var/log/zabbix

再度起動してみると問題なく立ち上がるようになりました。

ps で見ると、ld-linux ばかり並んで何がなんだかわかりません。

$ ps axg | grep zabbix
  366 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2 /opt/zabbix/usr/sbin/zabbix_server_mysql -c /etc/zabbix/zabbix_server.conf
  371 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  372 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  373 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  374 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  375 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  376 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  377 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  378 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  379 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  380 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  381 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  382 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  383 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  384 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  385 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  389 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  390 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  391 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  392 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  393 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  394 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  395 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  406 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  407 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  408 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2
  409 ?        S      0:00 /opt/zabbix/lib64/ld-linux-x86-64.so.2

argv[0] がロードモジュール名になるようにして exec するラッパーを作りました。

zwrapper.py
#!/usr/bin/python

import sys
import os

LD_SO = "/opt/zabbix/lib64/ld-linux-x86-64.so.2"

ZROOT = "/opt/zabbix"
LIBRARY_PATH = [ "/lib64", "/usr/lib64", "/usr/lib64/mysql" ]

if (len(sys.argv) < 1):
    sys.exit(1)

#os.environ["LD_LIBRARY_PATH"] = ':'.join([ ZROOT + x for x in LIBRARY_PATH ])

args = [ sys.argv[0] ]
args.append("--library-path")
args.append(':'.join([ ZROOT + x for x in LIBRARY_PATH ]))

if (sys.argv[0][0] == '/'):
    path = "%s%s" % (ZROOT, sys.argv[0])
    args.append(path)
else:
    path = "%s/usr/bin/%s" % (ZROOT, sys.argv[0])
    if (os.access(path, os.X_OK)):
        args.append(path)
    else:
        path = "%s/usr/sbin/%s" % (ZROOT, sys.argv[0])
        if (os.access(path, os.X_OK)):
            printf >>stderr, "%s: not found\n" % sys.argv[0]
            sys.exit(1)
        args.append(path)
args.extend(sys.argv[1:])
os.execv(LD_SO, args)

このラッパーは /usr/sbin/zabbix_server_mysql として配置して、こちらを起動すると、/opt/zabbix/usr/sbin/zabbix_server_mysql_ を起動して、プロセス名が /usr/sbin/zabbix_server_mysql として見えるようになります。

註)通常プロセス名は Zabbix 側で役割を表すように書き換えられるのですが、ld-linux 経由でロードモジュールを起動したときは、書き換えができていないようです。

$ ps axg | grep zabbix
 3840 ?        S      0:00 /usr/sbin/zabbix_server_mysql --library-path /opt/zabbix/lib64:/opt/zabbix/usr/lib64:/opt/zabbix/usr/lib64/mysql /opt/zabbix/usr/sbin/zabbix_server_mysql
 3845 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3846 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3847 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3848 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3849 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3850 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3851 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3852 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3853 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3854 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3855 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3856 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3857 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3858 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3859 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3860 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3861 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3862 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3863 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3870 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3871 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3872 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3873 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3874 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3875 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3876 ?        S      0:00 /usr/sbin/zabbix_server_mysql
 3894 pts/1    S+     0:00 grep zabbix
ノート

LD_LIBRARY_PATH を指定して、ld-linux-x86-64.so.2 を起動すると、zabbix_server から起動される子プロセスにもこの環境変数が渡ってしまい、子プロセスの起動に失敗します。

たとえば、zabbix_agentd を LD_LIBRARY_PATH を指定する方法で起動すると、zabbix_agentd そのものは動くのですが、UserParameter で指定した外部プログラムの起動に失敗します。

$ zabbix_get -s 127.0.0.1 -k mysql.version
sh: relocation error: /opt/zabbix/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference

この例の relocation error は、 zabbix_agentd が execl("/bin/sh", "sh", "-c", command, NULL); で子プロセスを起動しようとして /bin/sh がエラーを出したものです。

最初スタートアップスクリプトでは、LD_LIBRARY_PATH を指定するようにしていましたが、ラッパープログラムでは --library-path 引数で指定するように変更しています。

ラッパーの配置とシンボリックリンクの作成

今回は rpm2cpio でパッケージを展開していますが、本来であればインストール時に

/usr/sbin/update-alternatives --install /usr/sbin/zabbix_server \
        zabbix-server /usr/sbin/zabbix_server_mysql 10

が実行されて、/usr/sbin/zabbix_server/usr/sbin/zabbix_server_mysql を指すようにシンボリックリンクが張られます。

ラッパープログラムでは、起動された名前をもとに実行するロードモジュールを決定しているため、つぎのようにシンボリックリンクを作成します。

cp zwrapper.py /usr/sbin/zwrapper
ln -s zwrapper /usr/sbin/zabbix_server_mysql
ln -s zwrapper /usr/sbin/zabbix_server
ln -s zabbix_server_mysql /opt/zabbix/usr/sbin/zabbix_server
ln -s zwrapper /usr/sbin/zabbix_agentd
ln /usr/sbin/zwrapper /usr/bin/zwrapper
ln -s zwrapper /usr/bin/zabbix_get
ln -s zwrapper /usr/bin/zabbix_sender
$ ls -l /usr/sbin/zabbix_server /usr/sbin/zabbix_server_mysql  /opt/zabbix/usr/sbin/zabbix_server /opt/zabbix/usr/sbin/zabbix_server_mysql
lrwxrwxrwx 1 root root      19 Mar  4 02:55 /opt/zabbix/usr/sbin/zabbix_server -> zabbix_server_mysql
-rwxr-xr-x 1 root root 1594952 Feb 28 16:24 /opt/zabbix/usr/sbin/zabbix_server_mysql
lrwxrwxrwx 1 root root       8 Mar  4 03:08 /usr/sbin/zabbix_server -> zwrapper
lrwxrwxrwx 1 root root       8 Mar  2 18:41 /usr/sbin/zabbix_server_mysql -> zwrapper
$ ls -l /usr/sbin/zabbix_agentd
lrwxrwxrwx 1 root root 8 Mar  2 18:41 /usr/sbin/zabbix_agentd -> zwrapper
$ ls -l /usr/bin/zabbix_get /usr/bin/zabbix_sender /usr/bin/zwrapper
lrwxrwxrwx 1 root root   8 Mar  2 18:38 /usr/bin/zabbix_get -> zwrapper
lrwxrwxrwx 1 root root   8 Mar  2 18:41 /usr/bin/zabbix_sender -> zwrapper
-rwxr-xr-x 2 root root 881 Mar  4 05:42 /usr/bin/zwrapper

このようにすると、スタートアッププログラムは変更なしで使えるようになります。
アーカイブから /etc/init.d に展開します。

# rpm2cpio zabbix-server-2.4.7-1.el6.x86_64.rpm | (cd / && cpio -imv ./etc/init.d/zabbix-server)
# rpm2cpio zabbix-agent-2.4.7-1.el6.x86_64.rpm | (cd / && cpio -imv ./etc/init.d/zabbix-agent)

web

PHP 5.4 以上が必要となります。remi リポジトリのパッケージを導入しました。
インストールしたパッケージは以下の通りです。

  • php-common-5.4.45-4.el6.remi.x86_64
  • php-5.4.45-4.el6.remi.x86_64
  • php-mysql-5.4.45-4.el6.remi.x86_64
  • php-bcmath-5.4.45-4.el6.remi.x86_64
  • php-xml-5.4.45-4.el6.remi.x86_64
  • httpd-tools-2.2.15-47.el6.centos.3.x86_64
  • httpd-2.2.15-47.el6.centos.3.x86_64
  • php-cli-5.4.45-4.el6.remi.x86_64
  • php-pdo-5.4.45-4.el6.remi.x86_64
  • php-mbstring-5.4.45-4.el6.remi.x86_64
  • php-gd-5.4.45-4.el6.remi.x86_64
  • php-ldap-5.4.45-4.el6.remi.x86_64

ディレクトリのシンボリックリンクを張っておきます。

# ln -s /opt/zabbix/usr/share/zabbix /usr/share/zabbix

GUI からの初期設定で作成されるファイルが書き込めるようにディレクトリのアクセス権を設定します。

# chgrp apache /etc/zabbix/web
# chmod g+w /etc/zabbix/web
# ls -ld /etc/zabbix/web
drwxrwx--- 2 root apache 4096 Mar  2 19:43 /etc/zabbix/web

/opt/zabbix/etc/httpd/conf.d/zabbix.conf にあるファイルは apache 2.4 用のものであるため、以下のように修正して /etc/httpd/conf.d/zabbix.conf に配置します。

/etc/httpd/conf.d/zabbix.conf.patch
diff -u zabbix.conf zabbix.conf
--- zabbix.conf       2016-02-28 16:05:33.000000000 +0900
+++ zabbix.conf       2016-03-04 14:49:45.345699170 +0900
@@ -7,7 +7,8 @@
 <Directory "/usr/share/zabbix">
     Options FollowSymLinks
     AllowOverride None
-    Require all granted
+    Order allow,deny
+    Allow from all

     <IfModule mod_php5.c>
         php_value max_execution_time 300
@@ -16,22 +17,42 @@
         php_value upload_max_filesize 2M
         php_value max_input_time 300
         php_value always_populate_raw_post_data -1
-        # php_value date.timezone Europe/Riga
+        php_value date.timezone Asia/Tokyo
     </IfModule>
 </Directory>

 <Directory "/usr/share/zabbix/conf">
-    Require all denied
+    Order deny,allow
+    Deny from all
+    <files *.php>
+        Order deny,allow
+        Deny from all
+    </files>
 </Directory>

 <Directory "/usr/share/zabbix/app">
-    Require all denied
+    Order deny,allow
+    Deny from all
+    <files *.php>
+        Order deny,allow
+        Deny from all
+    </files>
 </Directory>

 <Directory "/usr/share/zabbix/include">
-    Require all denied
+    Order deny,allow
+    Deny from all
+    <files *.php>
+        Order deny,allow
+        Deny from all
+    </files>
 </Directory>

 <Directory "/usr/share/zabbix/local">
-    Require all denied
+    Order deny,allow
+    Deny from all
+    <files *.php>
+        Order deny,allow
+        Deny from all
+    </files>
 </Directory>
# apachectl graceful

GUI からの初期設定

デフォルトでは iptable によるパケットフィルターがかかっています。
必要に応じて修正します。

iptables.patch
diff -u /etc/sysconfig/iptables  /etc/sysconfig/iptables
--- /etc/sysconfig/iptables.old 2016-03-02 05:23:33.296000000 +0900
+++ /etc/sysconfig/iptables     2016-03-03 03:21:08.606717140 +0900
@@ -8,6 +8,10 @@
 -A INPUT -p icmp -j ACCEPT
 -A INPUT -i lo -j ACCEPT
 -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
+-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
+-A INPUT -m state --state NEW -m tcp -p tcp --dport 10050 -j ACCEPT
+-A INPUT -m state --state NEW -m tcp -p tcp --dport 10051 -j ACCEPT
+-A INPUT -j REJECT --reject-with icmp-host-prohibited
 -A INPUT -j REJECT --reject-with icmp-host-prohibited
 -A FORWARD -j REJECT --reject-with icmp-host-prohibited
 COMMIT

ブラウザから http://zabbix-server/zabbix/ にアクセスします。

2.2 や 2.4 とはずいぶん雰囲気が変わりましたが、設定の流れはかわりません。

ノート

SElinux が有効になっていると、Zabbix サーバへのヘルスチェックを SElinux が阻止するために、Zabbix server is not running (サーバが停止している) という警告が出たままになります。

このとき audit.log を確認するとメッセージが出ています。

/var/log/audit/audit.log
type=AVC msg=audit(1456943266.076:612): avc:  denied  { name_connect } for  pid=15592 comm="httpd" dest=10051 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:zabbix_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1456943266.076:612): arch=c000003e syscall=42 success=no exit=-13 a0=c a1=7fa7401687e8 a2=10 a3=7fa73bf6814c items=0 ppid=15529 pid=15592 auid=1003 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=1 comm="httpd" exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)

httpd(php)からの通信を許可するようセキュリティーポリシーを変更するか、SELinux を無効(Permissive あるいは Disable) にすると解消します。

# setsebool -P httpd_can_network_connect 1

まとめ

rpm パッケージと、対応する glibc や libmysqlclient、libcurl を解いて配置して、ダイナミックリンカに、必要なライブラリパスを指定してロードモジュールを呼び出すようにすれば、問題なく動作することがわかりました。

展開が必要な CentOS 7 のパッケージは以下の通りです。

  • glibc-2.17-105.el7.x86_64.rpm
  • libcurl-7.29.0-25.el7.centos.x86_64.rpm
  • mariadb-libs-5.5.44-2.el7.centos.x86_64.rpm
  • net-snmp-libs-5.7.2-24.el7.x86_64.rpm

Web GUI は、2.2 や 2.4 の時と同様の設定で対応可能です。

今回は、実行できる環境を、力業でつくりましたが、SRPM からも、spec ファイルを少し修正すればビルドできました。このことは、CentOS6 で Zabbix 3.0 を動かす (自力ビルド編) で取り上げます。

補足

PatchELF を使用すると INTERP セクションを変更できるのでやってみました。

$ wget http://nixos.org/releases/patchelf/patchelf-0.8/patchelf-0.8.tar.bz2
$ tar xvfa patchelf-0.8.tar.bz2
$ ./configure --prefix=/usr/local
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking whether gcc and cc understand -c and -o together... yes
checking for g++... no
checking for c++... no
checking for gpp... no
checking for aCC... no
checking for CC... no
checking for cxx... no
checking for cc++... no
checking for cl.exe... no
checking for FCC... no
checking for KCC... no
checking for RCC... no
checking for xlC_r... no
checking for xlC... no
checking whether we are using the GNU C++ compiler... no
checking whether g++ accepts -g... no
checking dependency style of g++... none
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating tests/Makefile
config.status: creating patchelf.spec
config.status: executing depfiles commands
$ make
Making all in src
make[1]: Entering directory `/src/patchelf-0.8/src'
source='patchelf.cc' object='patchelf.o' libtool=no \
DEPDIR=.deps depmode=none /bin/sh ../build-aux/depcomp \
g++ -DPACKAGE_NAME=\"patchelf\" -DPACKAGE_TARNAME=\"patchelf\" -DPACKAGE_VERSION=\"0.8\" -DPACKAGE_STRING=\"patchelf\ 0.8\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"patchelf\" -DVERSION=\"0.8\" -I.      -c -o patchelf.o patchelf.cc
../build-aux/depcomp: line 761: exec: g++: not found
make[1]: *** [patchelf.o] Error 127
make[1]: Leaving directory `/src/patchelf-0.8/src'
make: *** [all-recursive] Error 1

ビルドに g++ が必要でした。configure でエラーを出してよ。

$ sudo yum install gcc-c++
$ ./configure --prefix=/usr/local
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking whether gcc and cc understand -c and -o together... yes
checking for g++... g++
checking whether we are using the GNU C++ compiler... yes
checking whether g++ accepts -g... yes
checking dependency style of g++... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating tests/Makefile
config.status: creating patchelf.spec
config.status: executing depfiles commands
$ make
Making all in src
make[1]: Entering directory `/src/patchelf-0.8/src'
g++ -DPACKAGE_NAME=\"patchelf\" -DPACKAGE_TARNAME=\"patchelf\" -DPACKAGE_VERSION=\"0.8\" -DPACKAGE_STRING=\"patchelf\ 0.8\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"patchelf\" -DVERSION=\"0.8\" -I.     -g -O2 -MT patchelf.o -MD -MP -MF .deps/patchelf.Tpo -c -o patchelf.o patchelf.cc
mv -f .deps/patchelf.Tpo .deps/patchelf.Po
g++  -g -O2   -o patchelf patchelf.o
make[1]: Leaving directory `/src/patchelf-0.8/src'
Making all in tests
make[1]: Entering directory `/src/patchelf-0.8/tests'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `/src/patchelf-0.8/tests'
make[1]: Entering directory `/src/patchelf-0.8'
make[1]: Nothing to be done for `all-am'.
make[1]: Leaving directory `/src/patchelf-0.8'
$ sudo make install
Making install in src
make[1]: Entering directory `/src/patchelf-0.8/src'
make[2]: Entering directory `/src/patchelf-0.8/src'
 /bin/mkdir -p '/usr/local/bin'
  /usr/bin/install -c patchelf '/usr/local/bin'
make[2]: Nothing to be done for `install-data-am'.
make[2]: Leaving directory `/src/patchelf-0.8/src'
make[1]: Leaving directory `/src/patchelf-0.8/src'
Making install in tests
make[1]: Entering directory `/src/patchelf-0.8/tests'
make[2]: Entering directory `/src/patchelf-0.8/tests'
make[2]: Nothing to be done for `install-exec-am'.
make[2]: Nothing to be done for `install-data-am'.
make[2]: Leaving directory `/src/patchelf-0.8/tests'
make[1]: Leaving directory `/src/patchelf-0.8/tests'
make[1]: Entering directory `/src/patchelf-0.8'
make[2]: Entering directory `/src/patchelf-0.8'
make[2]: Nothing to be done for `install-exec-am'.
 /bin/mkdir -p '/usr/local/share/doc/patchelf'
 /usr/bin/install -c -m 644 README '/usr/local/share/doc/patchelf'
 /bin/mkdir -p '/usr/local/share/man/man1'
 /usr/bin/install -c -m 644 patchelf.1 '/usr/local/share/man/man1'
make[2]: Leaving directory `/src/patchelf-0.8'
make[1]: Leaving directory `/src/patchelf-0.8'

できた

$ /usr/local/bin/patchelf
syntax: /usr/local/bin/patchelf
  [--set-interpreter FILENAME]
  [--print-interpreter]
  [--set-rpath RPATH]
  [--shrink-rpath]
  [--print-rpath]
  [--force-rpath]
  [--remove-needed LIBRARY]
  [--debug]
  [--version]
  FILENAME

よし。

$ sudo /usr/local/bin/patchelf  --set-interpreter /opt/zabbix/lib64/ld-linux-x86-64.so.2 --set-rpath /opt/zabbix/lib64:/opt/zabbix/usr/lib64:/opt/zabbix/usr/lib64/mysql /opt/zabbix/usr/bin/zabbix_get
$ /opt/zabbix/usr/bin/zabbix_get -h
usage:
  zabbix_get -s host-name-or-IP [-p port-number] [-I IP-address] -k item-key
  zabbix_get -s host-name-or-IP [-p port-number] [-I IP-address]
                --tls-connect cert --tls-ca-file CA-file
                [--tls-crl-file CRL-file] [--tls-agent-cert-issuer cert-issuer]
                [--tls-agent-cert-subject cert-subject]
                --tls-cert-file cert-file --tls-key-file key-file -k item-key
  zabbix_get -s host-name-or-IP [-p port-number] [-I IP-address]
                --tls-connect psk --tls-psk-identity PSK-identity
                --tls-psk-file PSK-file -k item-key
  zabbix_get -h
  zabbix_get -V

Get data from Zabbix agent.

General options:
  -s --host host-name-or-IP  Specify host name or IP address of a host
  -p --port port-number      Specify port number of agent running on the host
                             (default: 10050)
  -I --source-address IP-address   Specify source IP address

  -k --key item-key          Specify key of the item to retrieve value for

  -h --help                  Display this help message
  -V --version               Display version number

TLS connection options:
  --tls-connect value        How to connect to agent. Values:
                               unencrypted - connect without encryption
                                             (default)
                               psk         - connect using TLS and a pre-shared
                                             key
                               cert        - connect using TLS and a
                                             certificate

  --tls-ca-file CA-file      Full pathname of a file containing the top-level
                             CA(s) certificates for peer certificate
                             verification

  --tls-crl-file CRL-file    Full pathname of a file containing revoked
                             certificates

  --tls-agent-cert-issuer cert-issuer   Allowed agent certificate issuer

  --tls-agent-cert-subject cert-subject   Allowed agent certificate subject

  --tls-cert-file cert-file  Full pathname of a file containing the certificate
                             or certificate chain

  --tls-key-file key-file    Full pathname of a file containing the private key

  --tls-psk-identity PSK-identity   Unique, case sensitive string used to
                             identify the pre-shared key

  --tls-psk-file PSK-file    Full pathname of a file containing the pre-shared
                             key

Example(s):
  zabbix_get -s 127.0.0.1 -p 10050 -k "system.cpu.load[all,avg1]"

  zabbix_get -s 127.0.0.1 -p 10050 -k "system.cpu.load[all,avg1]" \
    --tls-connect cert --tls-ca-file /home/zabbix/zabbix_ca_file \
    --tls-agent-cert-issuer \
    "CN=Signing CA,OU=IT operations,O=Example Corp,DC=example,DC=com" \
    --tls-agent-cert-subject \
    "CN=server1,OU=IT operations,O=Example Corp,DC=example,DC=com" \
    --tls-cert-file /home/zabbix/zabbix_get.crt \
    --tls-key-file /home/zabbix/zabbix_get.key

  zabbix_get -s 127.0.0.1 -p 10050 -k "system.cpu.load[all,avg1]" \
    --tls-connect psk --tls-psk-identity "PSK ID Zabbix agentd" \
    --tls-psk-file /home/zabbix/zabbix_agentd.psk

Report bugs to: <https://support.zabbix.com>
Zabbix home page: <http://www.zabbix.com>
Documentation: <https://www.zabbix.com/documentation>

これでラッパーも不要になります。

他のロードモジュールも書き換えます。

$ sudo /usr/local/bin/patchelf  --set-interpreter /opt/zabbix/lib64/ld-linux-x86-64.so.2 --set-rpath /opt/zabbix/lib64:/opt/zabbix/usr/lib64:/opt/zabbix/usr/lib64/mysql /opt/zabbix/usr/bin/zabbix_sender
$ /opt/zabbix/usr/bin/zabbix_sender -h | head
usage:
  zabbix_sender [-v] -z server [-p port] [-I IP-address] -s host -k key
                -o value
  zabbix_sender [-v] -z server [-p port] [-I IP-address] [-s host] [-T] [-r]
                -i input-file
  zabbix_sender [-v] -c config-file [-z server] [-p port] [-I IP-address]
                [-s host] -k key -o value
  zabbix_sender [-v] -c config-file [-z server] [-p port] [-I IP-address]
                [-s host] [-T] [-r] -i input-file
  zabbix_sender [-v] -z server [-p port] [-I IP-address] -s host
$ sudo /usr/local/bin/patchelf  --set-interpreter /opt/zabbix/lib64/ld-linux-x86-64.so.2 --set-rpath /opt/zabbix/lib64:/opt/zabbix/usr/lib64:/opt/zabbix/usr/lib64/mysql /opt/zabbix/usr/sbin/zabbix_server_mysql
$ /opt/zabbix/usr/sbin/zabbix_server_mysql -h | head
usage:
  zabbix_server_mysql [-c config-file]
  zabbix_server_mysql [-c config-file] -R runtime-option
  zabbix_server_mysql -h
  zabbix_server_mysql -V

The core daemon of Zabbix software.

Options:
  -c --config config-file        Absolute path to the configuration file
$ sudo /usr/local/bin/patchelf  --set-interpreter /opt/zabbix/lib64/ld-linux-x86-64.so.2 --set-rpath /opt/zabbix/lib64:/opt/zabbix/usr/lib64:/opt/zabbix/usr/lib64/mysql /opt/zabbix/usr/sbin/zabbix_agentd
$ /opt/zabbix/usr/sbin/zabbix_agentd -h | head
usage:
  zabbix_agentd [-c config-file]
  zabbix_agentd [-c config-file] -p
  zabbix_agentd [-c config-file] -t item-key
  zabbix_agentd [-c config-file] -R runtime-option
  zabbix_agentd -h
  zabbix_agentd -V

A Zabbix daemon for monitoring of various server parameters.

OK!

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5