New Relic メモ 〜 Infrastructure(CentOS + MySQL) と APM(Node.js) のAgentを導入

## きっかけ

* 仕事で触ることになりそうなので、入門してみることに

##ゴール

タイトルの通りだけど、

* Node.js + MySQL な環境にAPM とInfrastructure のAgent を放り込む
    * 各々の機能については、[New Relicのサイト](https://newrelic.com/)へ
* New Relic 側でメトリクスを拾える

## サーバの情報

以下の環境を用意する。（全部、ローカルネットワーク）

* APサーバ
    * CentOS 7
    * Apache
    * Node.js(Express.js)
    * hostname を`apserver.example.com`とする
* DBサーバ
    * CentOS 7
    * MySQL
    * hostname を`dbserver.example.com`とする

## DBサーバの準備

### MySQL のインストール、初期設定

yum のリポジトリを追加して、インストールする。

```shell-session
# yum install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
# yum -y install mysql-community-server
```

systemd にenable で登録、MySQL を起動する。

```shell-session
# systemctl enable mysqld.service
# systemctl start mysqld.service
```

Password はログに書いてある。

```shell-session
# cat /var/log/mysqld.log | grep gene
2020-08-13T13:30:27.617777Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: ************
```

Node.js から接続するユーザを作成、アクセス権限を与えておく。

```shell-session
mysql> create user 'apusr'@'%' identified with mysql_native_password by 'P@ssw0rd';
Query OK, 0 rows affected (0.01 sec)
mysql> grant all on *.* to 'apusr'@'%';
Query OK, 0 rows affected (0.01 sec)
```

## APサーバの準備

### npm と express.js のインストール

node.js v10を使用。リポジトリに追加する。

```shell-session
# curl -sL https://rpm.nodesource.com/setup_10.x | sudo bash -
... snip ...
```

nodejs をインストールする。

```shell-session
# yum install -y nodejs
... snip ...
# node -v
v10.22.0
# npm -v
6.14.6
```

アプリケーション配置先の設置をする。

```shell-session
# mkdir ~/express_crud/
# cd ~/express_crud/
```

プロジェクトの初期化とexpress.js のインストールをする。

```shell-session
# npm init -y
# npm install express
```

### node プロセスの常駐化

forever をインストールする。

```shell-session
# npm install -g forever
```

### テスト用app.js で起動する

以下のapp.jsを配置する。

```javascript:app.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

forever で起動して、疎通確認する。

```shell-session
# forever start app.js
# curl http://localhost:3000
Hello World!#
```

### mysql モジュールを入れて、接続確認

app.js を改造する。

```javascript:app.js
const express = require('express')
const app = express()
const port = 3000

const mysql = require('mysql');
const con = mysql.createConnection({
  host: 'dbserver.example.com',
  user: 'apusr',
  password: 'P@ssw0rd'
});

con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
});

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

node で起動して、疎通確認する。（Connected が出力されていたら成功）

```shell-session
# node app.js 
Example app listening on port 3000!
Connected
```

## app.js からデータ参照する

先ず、DBサーバ側でデータベースを作って、テーブルを作る。
作るTable は`id`、`name`、`email`をカラムに持ち、`id`は自動インクリメントとする。

```shell-session
mysql> create database datastore;
Query OK, 1 row affected (0.01 sec)
mysql> connect datastore;
Connection id:    40
Current database: datastore
mysql> create table users (id INT NOT NULL PRIMARY
KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL);
Query OK, 0 rows affected (0.02 sec)
```

ユーザを1匹追加しておく。

```shell-session
mysql> insert into users (name, email) values ('user1', 'user1@example.com');
Query OK, 1 row affected (0.00 sec)
mysql> select * from users;
+----+-------+-------------------+
| id | name  | email             |
+----+-------+-------------------+
|  1 | user1 | user1@example.com |
+----+-------+-------------------+
1 row in set (0.00 sec)
```

APサーバ側でapp.js を改造する。

```javascript:app.js
const express = require('express')
const app = express()
const port = 3000

const mysql = require('mysql');
const con = mysql.createConnection({
  host: 'dbserver.example.com',
  user: 'apusr',
  password: 'P@ssw0rd',
  database: 'datastore'
});

con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
});

app.get('/', (request, response) => {
        const sql = "select * from users"
        con.query(sql, function (err, result, fields) {  
        if (err) throw err;
        response.send(result)
        });
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

forever で起動して、動作確認する。

```shell-session
# forever start app.js
... snip ...
info:    Forever processing file: app.js
# curl http://localhost:3000/
[{"id":1,"name":"user1","email":"user1@example.com"}]
```

## app.js からデータ登録する

body-parser モジュールを突っ込む。

```shell-session
# npm install body-parser
```

app.js を改造する。

* body-parser を使う
* `/`にPOST用のエンドポイントを追加

```javascript:app.js

const express = require('express')
const app = express()
const port = 3000
const mysql = require('mysql');
const con = mysql.createConnection({
  host: 'dbserver.example.com',
  user: 'apusr',
  password: 'P@ssw0rd',
  database: 'datastore'
});
const path = require('path')
const bodyParser = require('body-parser')

app.use(bodyParser.urlencoded({ extended: true }));

con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
});

app.get('/', (request, response) => {
  const sql = "select * from users"
  con.query(sql, function (err, result, fields) {  
  if (err) throw err;
  response.send(result)
  });
});

app.post('/', (req, res) => {
  const sql = "INSERT INTO users SET ?"
  con.query(sql,req.body,function(err, result, fields){
    if (err) throw err;
    res.send('Completed Registration');
  });
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

forever で起動して、動作確認する。

```shell-session
# forever start app.js
... snip ...
info:    Forever processing file: app.js
# curl -X POST -d 'name=user10&email=user10@example.com' http://localhost:3000/
Completed Registration
# curl -s http://localhost:3000/ | jq
[
  {
    "id": 1,
    "name": "user1",
    "email": "user1@example.com"
  },
  {
    "id": 2,
    "name": "user10",
    "email": "user10@example.com"
  }
]
```

## app.js からデータ削除する

app.js を改造する

* `/`にDELETE用のエンドポイントを追加

```javascript:app.js
const express = require('express')
const app = express()
const port = 3000
const mysql = require('mysql');
const con = mysql.createConnection({
  host: 'dbserver.example.com',
  user: 'apusr',
  password: 'P@ssw0rd',
  database: 'datastore'
});
const path = require('path')
const bodyParser = require('body-parser')

app.use(bodyParser.urlencoded({ extended: true }));

con.connect(function(err) {
  if (err) throw err;
  console.log('Connected');
});

app.get('/', (request, response) => {
  const sql = "select * from users"
  con.query(sql, function (err, result, fields) {  
  if (err) throw err;
  response.send(result)
  });
});

app.post('/', (req, res) => {
  const sql = "INSERT INTO users SET ?"
  con.query(sql,req.body,function(err, result, fields){
    if (err) throw err;
    res.send('Completed Registration');
  });
});

app.delete('/',(req,res)=>{
  const sql = "DELETE FROM users WHERE id = ?";
  con.query(sql,[req.query.id],function(err,result,fields){
    if (err) throw err;
    res.send('Completed Deletion');
  })
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

forever で起動して、動作確認する。

```shell-session
# forever start app.js
... snip ...
info:    Forever processing file: app.js

# curl -X DELETE http://localhost:3000/?id=2
Complete Deletion
# curl -s http://localhost:3000/ | jq
[
  {
    "id": 1,
    "name": "user1",
    "email": "user1@example.com"
  }
]
```

## app.js からデータ更新する

飽きてきたので、パス...。

## OSのメトリクスを拾う設定を入れる（Infrastructure Agentの導入）

[New Relic One](https://one.newrelic.com/launcher/nr1-core.home)のホームの*Add your data*から`CentOS`を選択。セットアップ手順が表示される。

![FD5E3E46-B1D7-4938-9F9F-A8B737D36874.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/eaad7b94-aada-7bda-78fe-9b6f1d20e14c.jpeg)

*3 Deploy the agent*に表示されている手順を実行する。（ベタばりでOK）

```shell-session
# Create a configuration file and add your license key \
echo "license_key: 6db ... snip ... RAL" | sudo tee -a /etc/newrelic-infra.yml && \
\
# Create the agent’s yum repository \
sudo curl -o /etc/yum.repos.d/newrelic-infra.repo https://download.newrelic.com/infrastructure_agent/linux/yum/el/7/x86_64/newrelic-infra.repo && \
\
# Update your yum cache \
sudo yum -q makecache -y --disablerepo='*' --enablerepo='newrelic-infra' && \
\
# Run the installation script \
sudo yum install newrelic-infra -y
```

systemd にも反映済み。

```
# systemctl status newrelic-infra
● newrelic-infra.service - New Relic Infrastructure Agent
   Loaded: loaded (/etc/systemd/system/newrelic-infra.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2020-08-15 13:02:13 JST; 50s ago
```

当手順をAPサーバ、DBサーバともに実施する。
実施後、New Relic One のInfrastructure のページから2つのホストが追加されていることを確認できる。

![0C470F9D-AA11-44AB-86B2-CF685830D0E6.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/b8ac9117-1520-d302-41cd-0447bc915a32.jpeg)

## MySQL のメトリクスを拾う設定を入れる

[ここのページ](https://docs.newrelic.com/docs/integrations/host-integrations/host-integrations-list/mysql-monitoring-integration)に記載の手順に則って、コマンドを実行する

```shell-session
# yum -q makecache -y --disablerepo='*' --enablerepo='newrelic-infra'
# yum install nri-mysql
... snip ...
  nri-mysql.x86_64 0:1.4.0-1                       
完了しました!
# mysql -u root -p -e "CREATE USER 'newrelic'@'localhost' IDENTIFIED BY 'P@ssw0rd';"
Enter password:
# cd /etc/newrelic-infra/integrations.d/
# cp mysql-config.yml.sample mysql-config.yml
```

`mysql-config.yaml`を編集する。
一つ前の手順で作成したDBのユーザ情報を入れるだけ。

```yaml:mysql-config.yaml

integration_name: com.newrelic.mysql

instances:
  - name: mysql-server
    command: status
    arguments:
        hostname: localhost
        port: 3306
        username: newrelic
        password: P@ssw0rd
        # New users should leave this property as `true`, to identify the
        # monitored entities as `remote`. Setting this property to `false` (the
        # default value) is deprecated and will be removed soon, disallowing
        # entities that are identified as `local`.
        # Please check the documentation to get more information about local
        # versus remote entities:
        # https://github.com/newrelic/infra-integrations-sdk/blob/master/docs/entity-definition.md
        remote_monitoring: true
    labels:
        env: production
        role: write-replica
```

Agent を再起動する。

```shell-session
# systemctl restart newrelic-infra
```

New Relic One のInfrastructure のページからMySQLがが追加されていることを確認できる。

![30FD6F44-FE0C-4E6A-9DC5-77BA639AB5E7.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/09f61a53-d49d-3999-7456-a294396dbfa1.jpeg)

## Node.js のメトリクスを拾う設定を入れる（APM Agentの導入）

New Relic One のAPM のページから、Node.js のアプリケーションを追加する手順を参照する。

![27259543-0B9B-4D3E-870D-42FFAE2E5621.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/8672bb92-9eab-8770-ec15-a46941d5e2f9.jpeg)

手順に記載の通り実行する。

1 - アプリケーションの名前は、`express-crud`とする
2 - モジュールをインストールする

```shell-session
# cd ~/express_crud
# npm install newrelic --save
```

3 - 生成されたjavascriptを`~/express_crud/`以下に、`newrelic.js`として保存する。

4 - `app.js`からrequire する。

```javascript
require('newrelic');
```

5 - forever で起動する。

```shell-session
# forever start app.js 
... snip ...
info:    Forever processing file: app.js
```

6 - New Relic One のAPM のページから、`express-crud`が追加されていることを確認できる。

![15AF36E9-609C-497A-94CA-70F025796DCE.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/cfe6c319-bf1a-55dd-c46c-0a4286ba3b64.jpeg)

## おわり

結構簡単に導入できた。
この後、リクエスト発生させながら、どういうメトリクスが取れるか眺める。