- 投稿日:2019-05-26T23:57:50+09:00
MySQLレプリケーション遅延と不整合を体験してみよう
本稿ではDockerを使って、MySQLのレプリケーションを手軽に試すことができる環境を構築します。そしてレプリケーション遅延と不整合を実際に起こしてみて、どのような状態になるかを確認していきます。
具体的には
SHOW SLAVE STATUS
クエリの下記の値を確認することで、レプリケーションの状態がどうなっているかを見ていきます。
Slave_IO_Running
、Slave_SQL_Running
Last_Error
Seconds_Behind_Master
ここ数年でクラウドサービスのフルマネージドデータベースが主流になってきており、そこでは特にレプリケーションの知識が無くても簡単にレプリカを作成することができます。手動でMySQLレプリケーションを構築しなければいけない機会は減ってきているかもしれません。しかし裏側で動いている仕組みを知っておくと、何かの役に立つかもしれません。
たとえば AWS RDS 公式ドキュメントでも、AWS 外部のデータベースにデータを移行する手段としてレプリケーションが紹介されています。
参考: Aurora と MySQL との間、または Aurora と別の Aurora DB クラスターとの間のレプリケーション - Amazon Auroraまた一方で、Dockerを使うことによって、MySQLレプリケーションの検証環境を構築するのも非常に楽になりました。実際にレプリケーション遅延も簡単に体験することができます。
環境
- Windows 10 Pro 1809 Windows Subsystem for Linux (Bash on windows) Ubuntu 16.04.4 LTS
- たぶんMacでも同じです
- Docker version 18.09.1
- docker-compose version 1.18.0
前提知識
- MySQLで簡単なCLUDのSQLが書けること
- MySQLレプリケーションの基本について理解していること
- 具体的には、下記用語についてなんとなくでも良いので理解していること
- マスター(Master)サーバ
- スレーブ(Slave)サーバ
- バイナリログ(binlog)
本稿ではDockerを利用しますが、環境構築に利用するだけのため特に知識は無くても使えると思います。
1: レプリケーション構成MySQLを構築してみる
作業用に適当なディレクトリを作成します。
$ mkdir mysql-repl-sample && cd $_単一構成MySQLの構築
まずはレプリケーションを貼る前に、1台だけのMySQLを構築します。
Dockerfile-master
というファイルを作成します。後にSlave用のDockerfileも作成するため、名前を分けておきます。Dockerfile-masterFROM mysql:5.6 ADD ./mysql-master.cnf /etc/mysql/my.cnf RUN chmod 644 /etc/mysql/my.cnf
mysql-master.cnf
というファイルを作成します。こちらもmy.cnfですが、Slave用のファイルと区別できる名前にしています。mysql-master.cnf[mysqld] log-bin server-id=1設定について詳しくは下記を参照してください。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.2.4.2 バイナリログ形式の設定次に、Dockerfileとmy.cnfを使ってMySQLを起動するための、docker-compose.ymlを作成します。
docker-compose.ymlversion: '2' services: db-master: build: context: "./" dockerfile: "Dockerfile-master" ports: - "13306:3306" volumes: - mysql-master-data:/var/lib/mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 1 volumes: mysql-master-data:ファイルを作成し終えたら、下記コマンドでMySQLを起動できます。
$ docker-compose up -d13306ポートを開けているため、下記コマンドで接続できます。
$ mysql -u root -h 127.0.0.1 -P 13306接続できたら成功です。
2台のMySQLを構築
先程と同様に、Slave用のDockerfileとmy.cnfを作成します。
Dockerfile-slaveFROM mysql:5.6 ADD ./mysql-slave.cnf /etc/mysql/my.cnf RUN chmod 644 /etc/mysql/my.cnfmysql-slave.cnf[mysqld] server-id=2docker-compose.ymlは、下記のように書き換えます。
docker-compose.ymlversion: '2' services: db-master: build: context: "./" dockerfile: "Dockerfile-master" ports: - "13306:3306" volumes: - mysql-master-data:/var/lib/mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 1 db-slave: build: context: "./" dockerfile: "Dockerfile-slave" ports: - '23306:3306' volumes: - mysql-slave-data:/var/lib/mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 1 volumes: mysql-master-data: mysql-slave-data:Masterは13306、Slaveは23306ポートに設定しています。これは覚えておいてください。
ファイルを作成したら、下記コマンドでDockerコンテナを作り直します。
$ docker-compose down $ docker-compose up -dレプリケーションの開始
Master(13306ポート)に接続し、binlogの状態を確認します。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "SHOW MASTER STATUS\G"この時の「File」と「Position」の値をメモしておきます。
続いて Slave(23306ポート) に接続し、下記
CHANGE MASTER
とSTART SLAVE
というクエリを実行します。$ mysql -u root -h 127.0.0.1 -P 23306 > CHANGE MASTER TO MASTER_HOST='db-master', MASTER_PORT=3306, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='<MasterLogFile>', MASTER_LOG_POS=<MasterLogPosition>; > START SLAVE; > SHOW SLAVE STATUS\G
<MasterLogFile>
と<MasterLogPosition>
には、先ほどSHOW MASTER STATUS
でメモした値を入れてください。Slave(23306ポート)に接続し、
Slave_IO_Running
とSlave_SQL_Running
がともにYes
になっていることを確認できたら、レプリケーション成功です。$ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Running: Slave_IO_Running: Yes Slave_SQL_Running: Yesレプリケーションを使ってデータを2台のMySQLに反映させる
Master(13306ポート)に接続し、適当なデータを入れてみます。
$ mysql -u root -h 127.0.0.1 -P 13306> CREATE DATABASE sushi_ya; > USE sushi_ya; > CREATE TABLE sushi( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), INDEX(id) ); > INSERT into sushi_ya.sushi (name) VALUES ('maguro'); > INSERT into sushi_ya.sushi (name) VALUES ('tamago');つづいて Slave(23306ポート)に接続 し、データを確認してみましょう。
$ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi;" +----+--------+ | id | name | +----+--------+ | 1 | maguro | | 2 | tamago | +----+--------+SlaveにはINSERTクエリを実行していません。しかし、MasterにINSERTしたデータがSlaveにも入っていることを確認できました。これがレプリケーションです。
2: レプリケーション不整合を起こして、修復してみる
レプリケーション構成においてはデータの不整合が起きないようにするために、Masterに更新系クエリが実行される事が想定されています。Slaveにはロックをかけておいて更新を防ぐ事もあります。しかし何らかの理由でMasterとSlaveのデータに不整合が起きてしまった場合、レプリケーションが反映できず止まってしまう事があります。
実際にやってみましょう。
MasterとSlaveに同じIDで異なるデータを入れて、レプリケーションを止める
Slave(23306ポート)に直接 、id=100のデータを追記してみます。
$ mysql -u root -h 127.0.0.1 -P 23306 -e "INSERT into sushi_ya.sushi (id, name) VALUES (100, 'ika');"当然Slaveには追加されますが、Masterには反映されていません。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 100;" # Master$ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 100;" # Slave +-----+------+ | id | name | +-----+------+ | 100 | ika | +-----+------+ここで Master(13306ポート)にもid=100を追加する と、どうなるでしょうか?
$ mysql -u root -h 127.0.0.1 -P 13306 -e "INSERT into sushi_ya.sushi (id, name) VALUES (100, 'tako');"MasterとSlaveの様子を見てみます。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 100;" # Master +-----+------+ | id | name | +-----+------+ | 100 | tako | +-----+------+$ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 100;" # Slave +-----+------+ | id | name | +-----+------+ | 100 | ika | +-----+------+id=100なのに、異なるデータが入ってしまっており、データの不整合が起きています。
このときレプリケーションは止まってしまっています。
Master(13306ポート)にid=101のデータを追加しても、Slaveには反映されません。$ mysql -u root -h 127.0.0.1 -P 13306 -e "INSERT into sushi_ya.sushi (id, name) VALUES (101, 'ebi');" # Master $ mysql -u root -h 127.0.0.1 -P 13306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 101;" # Master $ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi WHERE id = 101;" # Slave
SHOW SLAVE STATUS
を確認します。$ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Last_Error Last_Error: Could not execute Write_rows event on table sushi_ya.sushi; Duplicate entry '100' for key 'PRIMARY', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log f4e6386f510a-bin.000001, end_log_pos 1351id=100のデータを反映させようとした時の
Duplicate entry
のエラーでレプリケーションが止まっています。$ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Running: Slave_IO_Running: Yes Slave_SQL_Running: No
Slave_SQL_Running: No
になっているのが確認できます。レプリケーションの再開
止まってしまったレプリケーションを再開するには、下記の方法があります
- エラーになっているクエリをスキップする (Slaveを採択)
- エラーを直してbinlogを通す (Masterを採択)
今回は、エラーを修正する方法を紹介します。
Slave(23306ポート)に入ってしまったid=100の不正なデータを削除し、Masterにあるid=100を反映できるようにします。そしt
START SLAVE
でレプリケーションを再開します。$ mysql -u root -h 127.0.0.1 -P 23306 -e "DELETE FROM sushi_ya.sushi WHERE id = 100;" $ mysql -u root -h 127.0.0.1 -P 23306 -e "START SLAVE;"エラーが解消され、レプリケーションが再開され、
SHOW SLAVE STATUS
もSlave_SQL_Running: Yes
になります。$ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Last_Error Last_Error:$ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Running: Slave_IO_Running: Yes Slave_SQL_Running: Yesレプリケーションが止まっていた後のid=101のデータも、全て入っているのが確認できます。
$ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi;" +-----+--------+ | id | name | +-----+--------+ | 1 | maguro | | 2 | tamago | | 100 | tako | | 101 | ebi | +-----+--------+今回とは逆にSlaveを採択する場合は、
SET GLOBAL sql_slave_skip_counter = 1
というクエリを実行しMasterのbinlogをスキップします。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.4.2.4 SET GLOBAL sql_slave_skip_counter 構文3: レプリケーション遅延を体験する
Masterに対して非常に重いクエリが実行されたりすると、レプリケーションに遅延が発生し「Masterのクエリが完了してからSlaveに反映されるまでの間、MasterにはレコードがあるのにSlaveには無い状態」になってしまいます。Slaveに参照系のクエリを投げて負荷分散している場合、更新したはずのデータが取得できず不具合を起こしてしまうことがあります。
実際にレプリケーション遅延を起こしてみましょう。
しかし今回の検証用のたった数件のデータベースでは、どんなクエリも一瞬で返ってきてしまうと思います。そこでここではSLEEP関数を使って「30秒かかる超遅いINSERTクエリ」を擬似的に発生させます。
まず準備として、Masterのバイナリログの形式をステートメントベースに変更しておきます。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "SET GLOBAL binlog_format = 'STATEMENT';"
SHOW VARIABLES
で確認できます。$ mysql -u root -h 127.0.0.1 -P 13306 -e "SHOW VARIABLES LIKE 'binlog_format'\G"; *************************** 1. row *************************** Variable_name: binlog_format Value: STATEMENTこれは本稿においてはSLEEP関数を使ってレプリケーション遅延を発生させるためだけの設定であり、本質とは関係ありません。詳細は下記を確認してください。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.2.4.2 バイナリログ形式の設定これから1分間で、下記の順番でオペレーションをします。
- Masterに「30秒かかるINSERT文」を実行する
- 30秒待つ
- Slaveに反映されるまでの30秒間に、下記を確認する
- Masterにデータが入っていること
- Slaveにデータが入っていないこと
SHOW SLAVE STATUS
でSeconds_Behind_Master
の値が1以上になること時間が限られているので、何が起きるか事前に頭に入れて準備した上で実行してみてください。
まずMaster(13306ポート)に「30秒かかるクエリ」を実行します。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "INSERT into sushi_ya.sushi (name) VALUES (CONCAT('wasabi', SLEEP(30)));" # 30秒待つMasterへのINSERTに30秒かかるのを待ったら、Slaveに反映される30秒の間に、下記クエリを実行してみましょう。
$ mysql -u root -h 127.0.0.1 -P 13306 -e "SELECT * FROM sushi_ya.sushi;" # Master $ mysql -u root -h 127.0.0.1 -P 23306 -e "SELECT * FROM sushi_ya.sushi;" # Slave $ mysql -u root -h 127.0.0.1 -P 23306 -e "SHOW SLAVE STATUS\G" | grep Seconds_Behind_MasterMasterに登録されたはずのデータが、Slaveに登録されていないのが分かります。
最後の
SHOW SLAVE STATUS
を何度も繰り返し実行していると、Seconds_Behind_Master
の数値がどんどん増えていって、30になると一気にゼロになると思います。30秒経過すると、Slaveにもデータが入っている事が確認できると思います。これがレプリケーション遅延です。レプリケーションが非同期のため起きる現象です。
このように
Seconds_Behind_Master
の値が大きくなっていると、MasterとSlaveとの間でデータに差異が生まれ、アプリケーションに不具合を引き起こす可能性があります。まとめ
MySQLレプリケーションの状態は、
SHOW SLAVE STATUS
クエリで確認することができます。その中でも不整合や遅延は、以下の値で確認できます。
Slave_IO_Running
、Slave_SQL_Running
がYesになっているかどうかLast_Error
があるかどうかSeconds_Behind_Master
が1以上の値になっていないかどうかなお今回使ったDockerでの検証環境構築プログラムは、下記リポジトリにもあります。
https://github.com/s2terminal/mysql-repl参考
- 投稿日:2019-05-26T23:16:44+09:00
nodejs製のwebアプリにnextjsを組み込むにはどのようにすればいいか試してみた
はじめに
以下の疑問点を解決するために試したことをまとめた。
- nodejs製のwebアプリにnextjsを組み込むにはどのようにすればいいか?
- DBに保存した値をSSRするためにはどのようにすればいいのか?
- ページの遷移はどのようにすればいいのか?
アプローチ
ユーザ別に登録できるタスクリストを作った。
repository
koajsでwebアプリの基盤を作り、typeormでDB操作を行う。
DBはdocker上にmysqlを立てた。
ormはtypeormを使用した。
その他細かいライブラリについてはpackage.jsonを参照。構成
全ユーザの一覧が見れるindexページと、タスクの一覧が見えるtaskページがある。
indexページではユーザとタスクのcrudが可能。
taskページではタスク一覧の読み取りのみ可能とした。データの構造は次の通り。
- server/entities/User.ts
- server/entities/Task.tsユーザを削除すると紐付いたタスクが自動的に削除されるように
Task.user
にonDelete: 'CASCADE'
オプションを付与した。nextに関係する処理があるファイルには★をつけた。
. ├── README.md ├── docker │ └── db │ └── my.conf ├── docker-compose.yml ├── models <- tableとapiの項目が一致するので同一のinterfaceを使用することにした │ ├── Task.ts │ └── User.ts ├── next.config.js ├── nodemon.json ├── ormconfig.json ├── package-lock.json ├── package.json ├── pages <- フロント側の処理。ちょっとnextが入ってるがほとんど普通のreact │ ├── index.tsx ★ │ └── tasks.tsx ★ ├── repositories <- api接続処理 │ ├── helpers.ts │ ├── tasks.ts │ └── users.ts ├── server │ ├── api <- controllerに当たる部分。apiリクエストに応答する処理。 │ │ ├── tasks.ts │ │ └── users.ts │ ├── commands <- サーバ処理とは一切関係ない。 │ │ ├── generateData.ts <- データを作る。 npm run generate:data で起動する。 │ │ └── route.ts <- routingを確認する。 npm run route で起動する。 │ ├── createRouter.ts ★ <- routerを作るentrypoint │ ├── entities <- tableの項目とマッピングするentity │ │ ├── Task.ts │ │ └── User.ts │ ├── helpers.ts <- DB接続処理がある。commandsでも使用するので分けた。 │ ├── index.ts ★ <- entrypoint。npm start で起動する。 │ ├── next.ts ★ <- nextを直接触る処理はここに閉じ込めた。 │ ├── repositories <- repositoriesと名前をつけたがuseCase層と合体させた。 │ │ ├── TaskRepository.ts │ │ └── UserRepository.ts │ └── views ★ <- controllerに当たる部分。 │ ├── index.ts │ └── tasks.ts ├── tsconfig.json └── tsconfig.server.json結論
1. nodejs製のwebアプリにnextjsを組み込むにはどのようにすればいいか?
1. 初期化処理で
prepare()
まずserverのentrypointである
server/index.ts
でprepare()
を行う。
それなりに時間がかかる処理なので、DB接続処理と同時に行うと時間節約できる。await Promise.all([ connectDatabase(), prepare() ])2. routingを設定する
すべてのリクエストにnextのhandleを設定する。
server/next.tsconst handler = app.getRequestHandler() // omit... export async function handle(ctx: BaseContext) { await handler(ctx.req, ctx.res) ctx.respond = false }server/createRouter.tsimport { handle } from './next' // omit... export default function () { const router = new Router() const assign = pipe( views, apiUsers, apiTasks, next ) return assign(router) } // omit... function next(router: Router) { return router .get('*', handle) // <- これが必要 }
router.get('*', handle)
で設定しないと以下のエラーが発生する。<-- GET /_next/on-demand-entries-ping?page=/ --> GET /_next/on-demand-entries-ping?page=/ 404 1ms - <-- GET /_next/static/development/pages/index.js?ts=1558873127678 --> GET /_next/static/development/pages/index.js?ts=1558873127678 404 0ms - <-- GET /_next/static/development/pages/_app.js?ts=1558873127678 --> GET /_next/static/development/pages/_app.js?ts=1558873127678 404 1ms - <-- GET /_next/static/runtime/webpack.js?ts=1558873127678 --> GET /_next/static/runtime/webpack.js?ts=1558873127678 404 1ms - <-- GET /_next/static/runtime/main.js?ts=1558873127678 --> GET /_next/static/runtime/main.js?ts=1558873127678 404 0ms - <-- GET /_next/static/development/dll/dll_599a58a60c43245180de.js?ts=1558873127678 --> GET /_next/static/development/dll/dll_599a58a60c43245180de.js?ts=1558873127678 404 0ms - <-- GET /_next/static/development/pages/index.js?ts=1558873127678 --> GET /_next/static/development/pages/index.js?ts=1558873127678 404 1ms - <-- GET /_next/static/development/pages/_app.js?ts=1558873127678 --> GET /_next/static/development/pages/_app.js?ts=1558873127678 404 1ms - <-- GET /_next/static/runtime/webpack.js?ts=1558873127678 --> GET /_next/static/runtime/webpack.js?ts=1558873127678 404 1ms - <-- GET /_next/static/runtime/main.js?ts=1558873127678 --> GET /_next/static/runtime/main.js?ts=1558873127678 404 0ms -一見viewへのリクエストに書けばいいように見える。
そこでserver/views/index.ts
に書いてみたが、await render(ctx.req, ctx.res, '/', query)
の後にawait handler(ctx.req, ctx.res)
を差し込めば上のエラーが、await render(ctx.req, ctx.res, '/', query)
より前に差し込めば下のエラーが発生する。fetch is not defined ReferenceError: fetch is not defined at _callee$ (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:886:13) at tryCatch (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:62:40) at Generator.invoke [as _invoke] (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:288:22) at Generator.prototype.(anonymous function) [as next] (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:114:21) at asyncGeneratorStep (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:186:24) at _next (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:208:9) at /Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:215:7 at new Promise (<anonymous>) at new F (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/core-js/library/modules/_export.js:36:28) at Module.<anonymous> (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:204:12) at Module.all (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:873:15) at _callee8$ (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:696:75) at tryCatch (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:62:40) at Generator.invoke [as _invoke] (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:288:22) at Generator.prototype.(anonymous function) [as next] (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/node_modules/regenerator-runtime/runtime.js:114:21) at asyncGeneratorStep (/Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/.next/server/static/development/pages/index.js:186:24)1. レンダリング処理を行う
次項(2. DBに保存した値をSSRするためにはどのようにすればいいのか?)で説明する。
2. DBに保存した値をSSRするためにはどのようにすればいいのか?
views/index.ts
とviews/tasks.ts
にあるようにrender関数に 文字列化した データを設定する。export default async function (ctx: BaseContext) { const users = await getRepository().all() const stringified = JSON.stringify(users) const query = { users: stringified } await render(ctx.req, ctx.res, '/', query) // <- ここで設定 ctx.respond = false }
pages/index.tsx
やpages/tasks.tsx
にある通り、getInitialProps
でデータを取得する。Page.getInitialProps = async (context: NextContext) => { const renderingOnServer = context.query.users !== undefined if (renderingOnServer) { // server処理の場合はqueryに文字列が設定されているので // JSON.parseして使用する。 const raw = context.query.users as string const users = JSON.parse(raw) return { users } } // ブラウザ側ならapiからデータを取得する。 const users = await UsersRepository.all() return { users } }生成されたhtmlを見てみると、以下のようにjsonが書き込まれていることがわかる。
<script id="__NEXT_DATA__" type="application/json">{"dataManager":"[]","props":{"pageProps":{"users":[{"id":1,"name":"山田","tasks":[{"id":1,"userId":1,"text":"皿洗い"},{"id":2,"userId":1,"text":"買い物"}]},{"id":2,"name":"佐藤","tasks":[{"id":3,"userId":2,"text":"草むしり"},{"id":4,"userId":2,"text":"押入れの片付け"}]},{"id":3,"name":"平田","tasks":[{"id":5,"userId":3,"text":"野菜の皮むき"}]},{"id":4,"name":"山本","tasks":[{"id":6,"userId":4,"text":"カレー作り"}]},{"id":5,"name":"柴田","tasks":[{"id":7,"userId":5,"text":"食卓の準備"},{"id":8,"userId":5,"text":"押入れの片付け"}]}]}},"page":"/","query":{"users":"[{\"id\":1,\"name\":\"山田\",\"tasks\":[{\"id\":1,\"userId\":1,\"text\":\"皿洗い\"},{\"id\":2,\"userId\":1,\"text\":\"買い物\"}]},{\"id\":2,\"name\":\"佐藤\",\"tasks\":[{\"id\":3,\"userId\":2,\"text\":\"草むしり\"},{\"id\":4,\"userId\":2,\"text\":\"押入れの片付け\"}]},{\"id\":3,\"name\":\"平田\",\"tasks\":[{\"id\":5,\"userId\":3,\"text\":\"野菜の皮むき\"}]},{\"id\":4,\"name\":\"山本\",\"tasks\":[{\"id\":6,\"userId\":4,\"text\":\"カレー作り\"}]},{\"id\":5,\"name\":\"柴田\",\"tasks\":[{\"id\":7,\"userId\":5,\"text\":\"食卓の準備\"},{\"id\":8,\"userId\":5,\"text\":\"押入れの片付け\"}]}]"},"buildId":"development","dynamicBuildId":false}</script>server側の処理のときならDBに接続できるのでは?と思い以下のようにしたがだめだった。
Page.getInitialProps = async (context: NextContext) => { const renderingOnServer = context.query.users !== undefined if (renderingOnServer) { const UserRepository = require('../server/repositories/UserRepository') const Container = require('typedi') const repository = Container.get(UserRepository) const users = await repository.all() return { users } // const raw = context.query.users as string // const users = JSON.parse(raw) // return { users } } const users = await UsersRepository.all() return { users } }./server/repositories/UserRepository.ts SyntaxError: /Users/h-h/h_h/2019-05-25-nextjs-koa-mysql/server/repositories/UserRepository.ts: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (6:1): 4 | import { User } from '../entities/User' 5 | > 6 | @Service() | ^ 7 | @EntityRepository(User) 8 | export class UserRepository { 9 |SyntaxErrorなのでbabelの設定次第では行けそうな気もするが、できたとしてもviewにDB操作するような処理を書くのは好きじゃないので考えるのをやめた。
3. ページの遷移はどのようにすればいいのか?
pages/index.tsx
やpages/tasks.tsx
にある通り次のようにする。import Link from 'next/Link' // omit... <Link href="/tasks"> <a>> tasks</a> </Link>参考資料
nextの組み方を調べたときに確認した。
- custom-server-koanextでtypescriptを使えるようにするために確認した。
- custom-server-typescript
- with-typescriptnodemonの設定方法を確認した。
- custom-server-nodemon
- 投稿日:2019-05-26T20:23:08+09:00
SSHのポートフォワーディングを学習して、SSH経由でMySQLサーバーに接続する
前置き
WorkbenchからSSHのみ接続許可されているMySQLサーバーに接続する事情がありましたので、SSHポートフォワーディングを利用しました。SSHポートフォワーディングは既に色々なところでご紹介されていますが、少しハマった箇所がありましたので記事にしたいと思います。なお、ポートフォワーディングの学習も兼ねてSSHコマンドとWorkbenchを分けましたが、Workbench単体で全てを設定させるより簡単な方法もあります。
構成
クライアント
名前 バージョン macOS Mojave 10.14.5 MySQL Workbench 8.0.15 RDBサーバー
名前 バージョン AWS EC2 Amazon Linux release 2 (Karoo) MySQL Ver 8.0.16 for Linux on x86_64 (MySQL Community Server - GPL) 今回はサンプル環境になるのでEC2にGlobal IP(Public IP)を割当てて、外部にSSHのポート22を解放しています。
MySQLのポートはデフォルトの3306を使用しており外部には解放してません。
実務で利用する環境では、既定のセキュリティポリシーと構成があると思います。登場するRDBサーバーの指定方法
以下、全て同じRDBサーバーを表します。後述の注意点で説明することになりますので、予め記載します。
名前 IP or 名前 備考 Global IP 18.179.xxx.xxx 既にインスタンスを破棄しました。
今後別の利用者に割り当てられるかもしれないので一部伏せておきます。Private IP 10.0.0.176 ループバック 127.0.0.1 もちろん、RDBサーバーから見た場合です。 ホスト localhost 〃 今回接続に使用したコマンド
最終的に目的を実現したコマンドは以下のとおりです。
sudo ssh -fNL 33060:localhost:3306 -i "hoge.pem" ec2-user@18.179.xxx.xxx
上記のイメージでSSHポートフォワーディングのコマンドを組み立てます。
- クライアント側の転送対象のポートを選ぶ
特に拘りがありませんでしたので、競合していない33060としました。- RDBサーバーから見た転送先を指定
SSHで接続したRDBサーバー(18.179.xxx.xxx)にMySQLが稼働しています。RDBサーバーから見ると転送先は自分自身になりますので、ホストがlocalhostで、ポートは3306になります。Global IPやPrivate IPで指定しない理由は後ほど解説します。その他、接続SSHへ接続するためのプライベートキーやユーザー、接続先の指定は通常のSSH接続と変わりありません。
Workbenchの設定
項目 IP or 名前 備考 Hostname 127.0.0.1 / localhost Port 33060 Username 任意のユーザー RDBサーバー(自分自身)から接続できるユーザーを指定します。
例えば特定IPからのみログインを許可しているユーザーはログインできません。Password 上記ユーザーのパスワード 無事にテスト接続ができました
ハマったところ
「ハマった」と言っても少しですが、躓いたところを記載します。
疎通ができない
当初、転送先にRDBサーバーのGlobal IPを指定していました。ssh -fNL 33060:18.179.xxx.xxx:3306 -i "web_a.pem" ec2-user@18.179.xxx.xxxネットワークの疎通については設定次第になりますが、RDBサーバーにログインして、以下のようにncコマンドを実行してみます。自身のGlobal IPとポート3306を指定して通信が疎通しない場合は、SSHポートフォワーディングもできません。
RDBサーバー側で疎通しない例$ nc -w 1 -vz 18.179.xxx.xxx 3306 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connection timed out.Private IPやループバック、localhostを指定することで、疎通ができるようになります。
RDBサーバー側で疎通する例$ nc -w 1 -vz 10.0.0.176 3306 Ncat: Version 7.50 ( https://nmap.org/ncat ) Ncat: Connected to 10.0.0.176:3306. Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.正しいパスワードを入力しているのにログインできない
疎通ができてもログインできないケースがあります。転送先にRDBサーバーのPrivate IPを指定しているとします。ssh -fNL 33060:10.0.0.176:3306 -i "web_a.pem" ec2-user@18.179.xxx.xxxログインユーザーが
'root'@'ip-10-0-0-176.ap-northeast-1.compute.internal'
になっています。筆者の環境では、他のサーバーからのrootユーザーのログインを禁止していますが、その時の設定は以下のとおりです。SELECT user, host FROM mysql.user WHERE user = 'root'; +------+-----------+ | user | host | +------+-----------+ | root | localhost | +------+-----------+ 1 row in set (0.00 sec)そのため、
'root'@'ip-10-0-0-176.ap-northeast-1.compute.internal'
ではログインできないことになります。SSHポートフォワーディングのためにログインユーザーを作成しない場合は、転送先にRDBサーバーに127.0.0.1
やlocalhost
を指定します。Workbenchのみで設定する
「いままでの説明は何だったんだ
」と思われるかもしれませんが、前述のとおりSSHコマンドを使わずにWorkbenchのみで設定する方法があります。今までSSHコマンドで実行した内容を再現すると以下のようになります。
参考
終わりに
Workbenchにポートフォワーディングの機能が含まれているので、SSHコマンドで別途実行する需要は無いかもしれません。しかし、SSHのポートフォワーディング単体を理解することで、どこかで応用ができるかもしれません。
- 投稿日:2019-05-26T19:58:18+09:00
【MySQL】起動しない時の対処方法
何が起きたか
- MySQLを起動しようとしたところ、エラーで起動しなくなった
- 入力したコマンドは、「sudo mysql.server start」
- ⇨ ERROR! The server quit without updating PID file
エラーメッセージ
sudo mysql.server start Password: Starting MySQL .. ERROR! The server quit without updating PID file (/usr/local/var/mysql/xxxxxxxxx.local.pid).どのように解決したか
- とりあえずググる
- 参考URL
$sudo chown -R _mysql:_mysql /usr/local/var/mysql
- 起動に成功
結果
$ sudo mysql.server start Starting MySQL .. SUCCESS!まとめ
- 解決しました
- 投稿日:2019-05-26T07:25:14+09:00
GCPの無料枠でWordPressを起動して運用するまでの備忘録 2
背景
前回GCPに Ubuntu 18.04 LTS Minimal のインスタンスを作成したので、そこに Docker をインストールして、WordPress を立ち上げます。
今回は、こちら ↓ を参考にさせて頂きました。
repositoryをアップデートする
apt パッケージのアップデート。
$ sudo apt update
HTTPS経由でrepositoryをやりとり出来るようにするためのパッケージをインストール。
$ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
Dockerの公式GPG keyを追加する。
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
repository( stable ) を追加する。
$sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
再度 apt パッケージのアップデート
$ sudo apt update
docker-ce をインストールする
インストール。
sudo apt install -y docker-ce
起動確認。
$ sudo systemctl status docker
プロセスの確認(sudo有)
$ sudo docker ps
指定した一般ユーザでも sudo 無しでdockerを使えるようにする
初期設定では出来ない
$ docker ps Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.37/containers/json: dial unix /var/run/docker.sock: connect: permission denied一般ユーザをDockerグループに追加する
### 確認 $ cat /etc/group | grep docker docker:x:999: ### 追加 sudo gpasswd -a xxxx docker ### 再確認 $ cat /etc/group | grep docker docker:x:999:xxxxdockerが使用するソケットを一般ユーザでも読み込み出来るようにする
$ sudo chmod 666 /var/run/docker.sock
プロセスの確認(sudo無)
$ docker ps
docker-composeをインストール
最新のバージョンを確認
$ export compose='1.24.0'
/usr/local/bin/ 配下にダウンロード$ sudo curl -L https://github.com/docker/compose/releases/download/${compose}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose実行権限の付与
$ sudo chmod 0755 /usr/local/bin/docker-compose
確認(sodo無)
$ docker-compose -v docker-compose version 1.24.0, build 0aa59064WordPress と Mysql インスタンスを個別に起動
プロジェクト用の空のディレクトリを作成
$ mkdir ghidorah_wordpress
$ cd ghidorah_wordpress
docker-compose.yml ファイルを作成version: '3' services: mysql: image: mysql:5.7 container_name: ghidorah-mysql restart: always environment: MYSQL_ROOT_PASSWORD: xxxx volumes: - "./.data/db:/var/lib/mysql" wordpress: image: wordpress:latest depends_on: - mysql container_name: ghidorah-wordpress restart: always ports: - 80:80 environment: WORDPRESS_DB_PASSWORD: xxxx volumes: - "./html:/var/www/html"docker-compose をバックグランドで起動
$ docker-compose up -d
WordPress が起動。確認のため、ブラウザでアクセスしてみる。
WordPress インストール画面が表示されていれば成功。
手順通りインストールを進める。
まとめ
WordPress が起ち上がったので、次回は独自ドメインの設定をします。