DigtalOceanでマーケティングオートメーションツールmauticを導入

#DigitalOceanにmauticを導入
格安と聞いて日本にはデータセンターはないが、とりあえず使ってみたかったDigitalOceanに、兼ねてより耳にしていたmauticを使ってみたかったので、二兎を追った。

##DigitalOceanについて
仮想サーバをDropletという単位で作成することができ、最も安価なDropletの構成（512MBメモリ・20GBのSSDディスク）で、28日で$5程度。
日本のデータセンタが利用できないのが残念だが、シンガポールのデータセンタが利用できるため、検証用としては十分だったので、利用することに。

DigitalOceanはこちらから→https://www.digitalocean.com/

登録はメールアドレス、パスワード、住所、決済情報で、5分も要さなかった。

##mauticについて
オープンソースのマーケティングオートメーション（MA）ツール。MAツールという言葉を仕事柄よく耳にすることが多くなったので、まず、どんなものか知りたかった。書籍等で勉強するのもありだったが、偶然にもこのオープンソースのMAツールがあることを知ったので、上述のDigitalOceanも試したいということもあり、Dropletを用意して、そこにmauticを導入して、試験的に利用することにした。

mauticはこちらから→https://www.mautic.org/

##インスール
`nginx + PHP7 + MySQL`の環境にした。

###Dropletの作成
OSはCentOS6.9(64bit)、5$の最小構成、データセンタはシンガポール、プライベートネットワークにチェック（結果的にこれは不要だった）、ssh公開鍵の登録し、hostnameを決めて作成。1分程度で完成し、仮想サーバに鍵認証でログインできた。

###iptablesの作成
検証とはいえ、最低限のセキュリティを確保するため、iptablesで簡単なファイアウォールの設定をした。

```shell-session:/root/tools/iptables.sh
#!/bin/sh

iptables -F INPUT
iptables -F OUTPUT
iptables -F FORWARD

iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
```

上記設定で、sshとhttp以外の通信は不許可にする。ただし、サーバからの応答についてはacceptするようにした。上記シェルスクリプトを実行することで、簡易的なファイアウォールを実現した。

```shell-session
$ /bin/sh /root/tools/iptables.sh
```

また、再起動したときのために/etc/rc.localに以下を追記する

```shell-session:/etc/rc.local(追記)
/bin/sh /root/tools/iptables.sh
```

###時刻の設定
####タイムゾーンの変更
タイムゾーンを日本時間に変更する。

```shell-session
$ cp /usr/share/zoneinfo/Japan /etc/localtime
```

####NTPサーバとの同期
`ntpd`を導入し、ソフトウェアクロックをSLEWモードで正確に保つようにする。

#####ntpdのインストール
```shell-session
$ yum install ntp
$ chkconfig ntpd on
```

####ntpdの設定
`ntp.conf`を下記のように記述。

```shell-session:/etc/ntp.conf
# For more information about this file, see the man pages
# ntp.conf(5), ntp_acc(5), ntp_auth(5), ntp_clock(5), ntp_misc(5), ntp_mon(5).

driftfile /var/lib/ntp/drift

# Permit time synchronization with our time source, but do not
# permit the source to query or modify the service on this system.
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery

# Permit all access over the loopback interface.  This could
# be tightened as well, but to do so would effect some of
# the administrative functions.
restrict 127.0.0.1
restrict -6 ::1

# Hosts on local network are less restricted.
#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap

# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
#server 0.centos.pool.ntp.org iburst
#server 1.centos.pool.ntp.org iburst
#server 2.centos.pool.ntp.org iburst
#server 3.centos.pool.ntp.org iburst

server -4 ntp.nict.jp
server -4 ntp1.jst.mfeed.ad.jp
server -4 ntp2.jst.mfeed.ad.jp
server -4 ntp3.jst.mfeed.ad.jp

#broadcast 192.168.1.255 autokey	# broadcast server
#broadcastclient			# broadcast client
#broadcast 224.0.1.1 autokey		# multicast server
#multicastclient 224.0.1.1		# multicast client
#manycastserver 239.255.254.254		# manycast server
#manycastclient 239.255.254.254 autokey # manycast client

# Enable public key cryptography.
#crypto

includefile /etc/ntp/crypto/pw

# Key file containing the keys and key identifiers used when operating
# with symmetric key cryptography.
keys /etc/ntp/keys

# Specify the key identifiers which are trusted.
#trustedkey 4 8 42

# Specify the key identifier to use with the ntpdc utility.
#requestkey 8

# Specify the key identifier to use with the ntpq utility.
#controlkey 8

# Enable writing of statistics records.
#statistics clockstats cryptostats loopstats peerstats
```

####ntpdの起動
```shell-session
$ /etc/init.d/ntpd start
```

####ハードウェアクロックへ反映
下記コマンドを実行する。

```shell-session
$ hwclock -w > /dev/null 2>&1
```

`cron`で下記のように追加をし、設定するのが楽。
下記例では、月曜日の午前3時に同期をとるようにしている。

```shell-session:/etc/crontab
0 3 * * 1 root /sbin/hwclock -w > /dev/null 2>&1
```

###swap（スワップ）領域の作成
dropletを作成しても、swap領域が0となっている。

```shell-session
$ free
             total       used       free     shared    buffers     cached
Mem:        501968     493816       8152      53488       3736      74344
-/+ buffers/cache:     415736      86232
Swap:            0          0          0
```

そのため、swap領域がない状態で運用すると、以下のようにMySQLでエラーが多発し、DBがダウンしてしまう現象が多発する。

```shell-session:/var/log/mysqld.log
017-06-23T16:52:56.670021Z 0 [ERROR] InnoDB: mmap(137428992 bytes) failed; errno 12
2017-06-23T16:52:56.670092Z 0 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
2017-06-23T16:52:56.670117Z 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error
2017-06-23T16:52:56.670145Z 0 [ERROR] Plugin 'InnoDB' init function returned error.
2017-06-23T16:52:56.670161Z 0 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2017-06-23T16:52:56.670175Z 0 [ERROR] Failed to initialize plugins.
2017-06-23T16:52:56.670196Z 0 [ERROR] Aborting
```

####ddコマンドでswap用ファイルを作成する
ここでは1GBのswapファイルを作成した。

```shell-session
$ dd if=/dev/zero of=/swapfile bs=1M count=1024

1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 2.83618 s, 379 MB/s
```

####swap領域を作成する
```shell-session
 mkswap /swapfile
mkswap: /swapfile: warning: don't erase bootbits sectors
        on whole disk. Use -f to force.
Setting up swapspace version 1, size = 1048572 KiB
no label, UUID=4cc2fb29-7e0d-4342-b888-d131c248febe
```

####swap領域を有効にする
```shell-session
$ swapon /swapfile
```

swap領域を有効にすることで、下記のようにスワップ領域が用意されていることが確認できる。

```shell-session
$ free
             total       used       free     shared    buffers     cached
Mem:        501968     495760       6208      53488       2728      76548
-/+ buffers/cache:     416484      85484
Swap:      1048572          0    1048572
```

####再起動したときもswap領域を有効にする
`/etc/fstab`を編集する。

```shell-session:/etc/fstab
UUID=3fda56ee-2072-4762-b2d8-51a806890759 /                       ext4    defaults        1 1
tmpfs                   /dev/shm                tmpfs   defaults        0 0
devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
sysfs                   /sys                    sysfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0
/swapfile               swap                    swap    defaults        0 0
```

###MySQL5.7のインストール
####インストール
```shell-session
$ rpm --import http://dev.mysql.com/doc/refman/5.7/en/checking-gpg-signature.html
$ rpm -ihv http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
$ yum --disablerepo=\* --enablerepo='mysql57-community*' list available
$ yum --enablerepo='mysql57-community*' install -y mysql-community-server
```

####設定ファイル

```shell-session:/etc/my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
default-storage-engine=innodb
innodb_large_prefix

sql_mode=ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock

symbolic-links=0

log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4
```

####起動
MySQL5.7を起動する。

```shell-session
$ /etc/init.d/mysqld start
```

####rootの初期パスワードの確認
下記のコマンドを実行し、初期パスワードを確認する

```shell-session
$ cat /var/log/mysqld.log | grep 'password is generated'
```

####rootのパスワードを変更
確認した初期パスワードでMySQL5.7にログインする。

```shell-session
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'LxAeT$#11op';
```

また、mauticで利用するデータベースを作成する。

データベースを作成
```shell-session
mysql> CREATE DATABASE mautic;
```

###MTAの確認
mailxをインストールし、テスト配信してみる。

```shell-session
$ yum install -y mailx
$ alternatives --display mta
$ echo "test" | mail -s "subject" myname@mydomain.com
```

###nginxのインストール
####インストール

```shell-session
$ rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
$ yum -y install nginx
```
####設定ファイル
```shell-session:/etc/nginx/nginx.conf
user  nginx;
worker_processes  1;


pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    error_log  /var/log/nginx/error.log warn;

    sendfile        off;
    #tcp_nopush     on;

    keepalive_timeout  65;

    fastcgi_read_timeout 180;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
```
```shell-session:/etc/nginx/conf.d/default.conf
server {
    listen 80 default;
    server_name _;
    root /var/www/html/mautic;
    index index.php index.html index.htm;
    charset utf-8;

    access_log /var/log/nginx/default.access.log;
    error_log /var/log/nginx/default.error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    sendfile off;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_intercept_errors on;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        include       fastcgi_params;
    }
}
```

###PHP7とphp-fpmのインストール
####インストール

```shell-session
$ yum install --enablerepo=remi-php70 -y \
      php \
      php-fpm \
      php-cli \
      php-mcrypt \
      php-common \
      php-devel \
      php-mbstring \
      php-mysqlnd \
      php-opcache \
      php-pdo \
      php-pear \
      php-pecl-apcu \
      php-pecl-zip \
      php-process \
      php-xml \
      php-intl \
      php-soap \
      php-pecl-memcache \
      php-pecl-memcached \
      php-couchbase \
      php-imap \
      php-json \
      php-curl \
      php-gd
```

####設定ファイル

```shell-session:/etc/php.ini(追記・変更)
default_charset = "UTF-8"
date.timezone = "Asia/Tokyo"

display_errors = On
display_startup_errors = On
log_errors = On

[mbstring]
mbstring.language = Japanese
```

```shell-session:/etc/php-fpm.d/www.conf(追記・変更)
user = nginx
group = nginx

listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

catch_workers_output = yes

php_flag[display_errors] = on
php_admin_value[display_errors] = 'stderr'
```

###node.js(npm)とgulp、webpackのインストール

```shell-session
$ yum install -y gcc-c++ make
$ curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash -
$ yum install nodejs
$ npm i -g grunt-cli
$ npm i -g browserify
$ npm i -g uglify-js
$ npm i -g webpack
```
###mauticの設置
gitとcomposerのインストール

```shell-session
$ yum install -y git
$ cd /tmp
$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer
$ composer self-update
```
事前に[github](https://github.com/mautic/mautic)にあるmauticをforkする。
その後、仮想サーバで以下のコマンドを実行し、forkしたmauticを`git clone`することで設置。mauticのバージョンを更新するときは、`git pull origin master`で、更新。

```shell-session
$ cd /var/www/html
$ git clone https://github.com/myacccount/mautic.git
$ cd mautic
$ git checkout master
$ composer install
```
###アクセス権限の変更
このままだとnginxを起動しても、ブラウザアクセスしたときにエラーが発生するので、権限を変更する。

```shell-session
$ touch /var/log/php-fpm/www-error.log
$ chmod 777 -R /var/log/php-fpm
$ chmod 777 -R /var/lib/php/session
$ chmod 777 -R /var/www/html/app/cache
$ chmod 777 -R /var/www/html/app/logs
```

###nginxとphp-fpmの起動
まずはphp-fpmから起動する。

```shell-session
$ /etc/init.d/php-fpm start
```
続いてnginxを起動する。

```shell-session
$ /etc/init.d/nginx start
```

##mautic起動
ブラウザアクセスするとmauticのデータベース設定、adminユーザ登録の画面が順次表示され、ダッシュボードのページが無事表示される。

<img width="1158" alt="スクリーンショット 2017-05-12 0.31.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/4ce970bb-2581-9653-2c9a-3e3cb49f193a.png">

※キャプチャ画像では日本語対応で表示されている。設定の詳細は[こちら](https://customjourney.marketing/setup_mautic)を参照した。ただし、一度ログアウトしないと日本語化されなかったので、動作仕様について調べてみたいと思う。

##その他

###アバター
mauticにログインするとアバター画像が表示されない状態になるので、気になるのであれば[gravatar](https://ja.gravatar.com/)で、アカウント作成時に指定したメールアドレスでgravatarに登録すれば（登録しても即時反映されなかった）正常に表示される。

###不具合関連
※実はMariaDBでmauticを動作させようとしたが、下記のPHPエラーが発生したため、断念し、Dropletを再作成した背景があった。

```php
[2017-05-02 11:55:53] mautic.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\Exception\DriverException: "An exception occurred while executing 'SELECT COUNT(t.date_added) AS leads, ll.id, ll.name FROM lead_lists_leads t INNER JOIN lead_lists ll ON ll.id = t.leadlist_id WHERE (ll.is_published = ?) AND (t.date_added BETWEEN ? AND ?) GROUP BY ll.id ORDER BY leads DESC LIMIT 9' with params [true, "2017-04-02 00:00:00", "2017-05-02 23:59:59"]:  SQLSTATE[42000]: Syntax error or access violation: 1055 'mautic.ll.name' isn't in GROUP BY" at /var/www/html/src/mautic/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php line 115 {"exception":"[object] (Doctrine\\DBAL\\Exception\\DriverException(code: 0): An exception occurred while executing 'SELECT COUNT(t.date_added) AS leads, ll.id, ll.name FROM lead_lists_leads t INNER JOIN lead_lists ll ON ll.id = t.leadlist_id WHERE (ll.is_published = ?) AND (t.date_added BETWEEN ? AND ?) GROUP BY ll.id ORDER BY leads DESC LIMIT 9' with params [true, \"2017-04-02 00:00:00\", \"2017-05-02 23:59:59\"]:\n\nSQLSTATE[42000]: Syntax error or access violation: 1055 'mautic.ll.name' isn't in GROUP BY at /var/www/html/src/mautic/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php:115, Doctrine\\DBAL\\Driver\\PDOException(code: 42000): SQLSTATE[42000]: Syntax error or access violation: 1055 'mautic.ll.name' isn't in GROUP BY at /var/www/html/src/mautic/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:93, PDOException(code: 42000): SQLSTATE[42000]: Syntax error or access violation: 1055 'mautic.ll.name' isn't in GROUP BY at /var/www/html/src/mautic/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:91)"} []
```

このエラーについて調べた結果は[こちら](http://qiita.com/hiroykam/items/7ab7ab088db68e593bc6)にまとめた。`GROUP BY`でカラム指定を省略してSELECTしたため。MariaDBとかMySQLとかではなく、`sql_mode`に`ONLY_FULL_GROUP_BY`したかどうかであった。