20201201のMySQLに関する記事は7件です。

【MySQL 8.0.22】 レプリケーション on Docker

Dockerを使ってMySQLのレプリケーション環境を構築していきたいと思います。

Dockerfileやdocker-composeを使った構築方法に関しては、結構たくさん記事があるので、
趣向を変えてコマンドだけで構築していきたいなと思います。

  • 環境
    • macOS Catalina ver: 10.15.17
    • Docker Engine ver: 19.03.13

完成図

スクリーンショット 2020-11-29 21.19.07のコピー.png

ネットワークの作成

「192.168.220.0/24」の範囲で「mysql_net」という名前のネットワークを構築します。
コマンドは以下です。

docker network create -d bridge --subnet=192.168.220.0/24 mysql_net

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

docker network ls -f "NAME=mysql_net"

bridgeモードについては以下の方の記事がわかりやすいです。

Docker network 概論 - Qiita

ボリュームの作成

replicationを行うのに必要なダンプを効率よく共有できるように共有するvolumeを準備します。
コマンドは以下です。

docker volume create --name shared-dump

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

docker volume ls -f "NAME=shared-dump"

コンテナの作成

今回は、現在最新バージョンである8.0.22を利用します。
以下のコマンドでイメージをpullしてください。

docker pull mysql:8.0.22

以下のコマンドでイメージがpullできていることを確認して下さい。

docker images mysql:8.0.22

MasterとSlaveのコンテナを一つずつ作成します。

# Master
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.50 \
           --name master \
           mysql:8.0.22

# Slave
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.100 \
           --name slave \
           mysql:8.0.22

MasterとSlaveによるコマンドの違いはほとんどありません。
一応、どちらがMasterでどちらがSlaveであるかをわかりやすくするためにコンテナの名前を変えています。異なる点としてはこのぐらいです。

ちょっとした検証でコンテナを立ち上げる際は、--rmオプションをつけて立ち上げるのが個人的に好きです(諸事情があって今回は付与できませんでしたが)。--rmオプションは停止と同時にコンテナを削除してくれるのでゴミが残らないところがポイントです。

設定ファイルを置き換える

設定ファイルの置き換えがrunサブコマンドでできればいいんですけどね。

Dockerfileやdocker-composeだとこの辺りの処理がとても楽ですよね。
この辺りがコマンドだと辛いですねー。

というわけで、レプリケーションに必要なserver-idの設定値をそれぞれの環境に入れていきたいと思います。

# master
docker exec master bash -c "echo 'server-id = 101' >> /etc/mysql/my.cnf"

# slave
docker exec slave bash -c "echo 'server-id = 102' >> /etc/mysql/my.cnf"

設定を更新するためにmysqlを再起動する必要があります。
ただ、dockerを再起動するとコンテナは停止しちゃうのでコンテナの再起動が必要です。
そのため今回は--rmオプションが使えませんでした。

以下のコマンドを実行して再起動します。

docker restart $(docker ps -aq -f "NAME=master" -f "NAME=slave")

確認には以下を実行してみてください。

for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'select @@server_id;'"
done

レプリケーション

レプリケーション以外の環境は概ねできました。

ここからはあまりdocker関係ないですが、レプリケーションの検証まで行ってみます。

検証用にデータベースを作成する

for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'create database repl;'"
done

レプリケーションを開始するための準備

ここから先の作業はcommandを渡して作業すると面倒なのでコンテナに入ることにします。

# 以下のコマンドでmasterコンテナに入ります。
docker exec -it master bash

# rootユーザでmysqlに入ります。
mysql -uroot -proot

適当なテーブルを作成し、データを投入します。

-- 適当なデータを投入します。
create table repl.users (id int unsigned not null auto_increment, name varchar(255) not null, primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into repl.users (name) values ("sample taro"), ("sample hanako");

-- slaveが接続するために必要なレプリケーションユーザを作成します
create user 'repl'@'192.168.220.100' identified with mysql_native_password by 'pQ8gDdzQHEtX@b8';
grant replication slave, replication client on *.* to 'repl'@'192.168.220.100';

-- 現在のマスターの状態を確認します。FileとPosition列の値をメモしてください
show master status;

quit

ダンプを取得します。

mysqldump -uroot -proot repl> /dump/repl_dump.sql

レプリケーションを開始する

次はslaveコンテナでの操作となりますので、まずはコンテナに入りましょう。

docker exec -it slave bash

レプリケーションを開始するまでの流れとしては以下です。

  1. dumpを取り込む
  2. レプリケーションの設定を行う
  3. レプリケーションを開始する

まずは、ダンプを取り込みます。

mysql -uroot -proot repl < /dump/repl_dump.sql

レプリケーションの設定を行う前にmasterと差分があることを確認しましょう(mysqlへログインしている前提です)。
masterでの作業でメモした「File」と「Position」の値を利用します。

-- replicationの設定を行います。
change master to
master_host='192.168.220.50',
master_user='repl',
master_password='pQ8gDdzQHEtX@b8',
master_log_file='binlog.000003',
master_log_pos=1518;

-- レプリケーションを開始します。
start replica;

start slaveコマンドは消されるようで今後はstart replicaを使うようです。

同期を確認する

現在のrepl.usersテーブルの状態は以下です。

mysql> select * from repl.users;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
+----+---------------+
2 rows in set (0.00 sec)

適当なデータを投入し、master,slave両方でテーブルの状態を確認してみます。

mysql> insert into repl.users (name) values ("takeshi"), ("tanaka"), ("sasaki");
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- master
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         101 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)

-- slave
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         102 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)

大丈夫そうですね。

最後に

本来はもう少しひねりたかったのですが、普通の検証になってしまいました(すみません)。

インフラ業務では、本番により近い環境で作業を行うかどうかで品質が大きく変わってしまいます。
なので、dockerをもっとうまく使いこなせればなーと日々感じますねー。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mariadb MaxScale使って、MySQLで持ってる個人情報のマスキングやってみた

なんでこんなことやったの?

  • 担当プロジェクトにてセキュリティ面強化における開発要望となったため調査したら、権限周りと連動させての実装方法として意外と良さそうだったから :thinking:
  • アプリケーションで対応する方法もあるとは思うけど、運用で度々変更を求められることが想定されたので、設定値変更くらいで変えられる仕組みがよかった :thumbsup:

とりあえず試してみた

使ったサーバが Amazon Linux 1 だったのもあり、公式サイトのインストール手順にそって対応するとエラーが出てEC2にはインストールできず・・・ :cry:

$ cat /etc/system-release
Amazon Linux AMI release 2018.03

$ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
[error] Detected RHEL or compatible but version () is not supported.
[error] The MariaDB Repository supports these Linux OSs, on x86-64 only:
    * RHEL/CentOS 6, 7, & 8
    * Ubuntu 16.04 LTS (xenial), 18.04 LTS (bionic), & 20.04 LTS (focal)
    * Debian 8 (jessie), 9 (stretch), & 10 (buster)
    * SLES 12 & 15
[error] See https://mariadb.com/kb/en/mariadb/mariadb-package-repository-setup-and-usage/#platform-support

Dockerイメージなら用意されてるみたいだったので、それでトライ :thumbsup:

とりあえずEC2にDockerインストールして、イメージ取ってきて、作ったconfigをコピーして docker run してSQL実行してみたらでけた :tada: :tada: :tada:

$ sudo yum update -y
$ sudo yum install -y docker
$ sudo service docker start
Starting cgconfig service:                                 [  OK  ]
Starting Docker:                                           [  OK  ]
$ sudo usermod -a -G docker ec2-user
$ docker pull mariadb/maxscale:latest
$ docker run -d -p 4008:4008 --name mxs -v /etc/maxscale.cnf:/etc/maxscale.cnf -v /etc/maxscale.cnf.d/masking_rules.json:/etc/maxscale.cnf.d/masking_rules.json mariadb/maxscale:latest
$ docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                    NAMES
XXXXXXXXXXXX        mariadb/maxscale:latest   "docker-entrypoint.s…"   7 minutes ago       Up 7 minutes        0.0.0.0:4008->4008/tcp   mxs

maxscale.cnfファイルは以下のような感じ :thumbsup:

maxscale.cnf
# MaxScale documentation on GitHub:
# https://github.com/mariadb-corporation/MaxScale/blob/2.1/Documentation/Documentation-Contents.md
# Global parameters
#
# Complete list of configuration options:
# https://github.com/mariadb-corporation/MaxScale/blob/2.1/Documentation/Getting-Started/Configuration-Guide.md
[maxscale]
threads=auto

# Server definitions
#
# Set the address of the server to the network
# address of a MySQL server.
#
[server1]
type=server
address=XX.XX.XX.XXX
port=3306
protocol=MySQLBackend

# Monitor for the servers
#
# This will keep MaxScale aware of the state of the servers.
# MySQL Monitor documentation:
# https://github.com/mariadb-corporation/MaxScale/blob/2.1/Documentation/Monitors/MySQL-Monitor.md
[MySQL-Monitor]
type=monitor
module=mysqlmon
servers=server1
user=XXXXXXXXX
password=XXXXXXXXX
monitor_interval=10000
failcount=3

[Masking]
type=filter
module=masking
rules=/etc/maxscale.cnf.d/masking_rules.json  ←こいつにマスキング周りのルールを書く形でした
warn_type_mismatch=always
large_payload=ignore

# Service definitions
#
# Service Definition for a read-only service and
# a read/write splitting service.
#
# ReadConnRoute documentation:
# https://github.com/mariadb-corporation/MaxScale/blob/2.1/Documentation/Routers/ReadConnRoute.md
[Read-Only-Service]
type=service
router=readconnroute
servers=server1
user=XXXXXXXXX
password=XXXXXXXXX
router_options=slave
filters=Masking

# This service enables the use of the MaxAdmin interface
# MaxScale administration guide:
# https://github.com/mariadb-corporation/MaxScale/blob/2.1/Documentation/Reference/MaxAdmin.md
[MaxAdmin-Service]
type=service
router=cli
# Listener definitions for the services
#
# These listeners represent the ports the
# services will listen on.
#
[Read-Only-Listener]
type=listener
service=Read-Only-Service
protocol=MySQLClient
port=4008
address=0.0.0.0
[MaxAdmin-Listener]
type=listener
service=MaxAdmin-Service
protocol=maxscaled
socket=default

マスキングルール(/etc/maxscale.cnf.d/masking_rules.json) はとりあえずこう :point_down:

/etc/maxscale.cnf.d/masking_rules.json
{
    "rules": [
        {
            "replace": {
                "database": "sample",
                "table": "user",
                "column": "user_name"
            },
            "with": {
                "fill": "X"
            }
        },
        {
            "replace": {
                "database": "sample",
                "table": "user",
                "column": "mail_address"
            },
            "with": {
                "fill": "@"
            }
        }
    ]
}

実際の結果

直接接続

$ mysql -u XXXXXXXX -p -h XX.XX.XX.XXX
Enter password: 
mysql> use sample
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed

mysql> select id,user_name,mail_address from user limit 3;
+----+---------------------+-----------------+
| id | user_name           | mail_address    |
+----+---------------------+-----------------+
|  1 | 山田太郎1            | yamada@test.com |
|  2 | スズキ 一太郎         | suzuki@test.com |
|  3 | 佐藤 二郎            | sato@test.com   |
+----+---------------------+-----------------+
3 rows in set (0.00 sec)

MaxScale経由で接続

$ mysql -u XXXXXXXX -p -h 172.17.0.2 -P 4008
Enter password: 
mysql> use sample
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed

mysql> select id,user_name,mail_address from user limit 3;
+----+---------------------+-----------------+
| id | user_name           | mail_address    |
+----+---------------------+-----------------+
|  1 | XXXXXXXXXXXXXXX     | @@@@@@@@@@@@@@@ |
|  2 | XXXXXXXXXXXXXXXX    | @@@@@@@@@@@@@@@ |
|  3 | XXXXXXXXXXXXXXXXXXX | @@@@@@@@@@@@@@@ |
+----+---------------------+-----------------+
3 rows in set (0.00 sec)

使ってみた感想

フルマスキングができたので、次は複数のマスキングルール試したらやれそう :thinking:
電話番号のマスキングやら住所も正規表現でやればマスキング自体はできそうですね :smile:
「対象文字列の一部はそのままがいい」とかワガママ案件でなければw :sweat_smile:

参考サイト

Mariadb MaxScale周り

その他のマスキング対応ツール

マスキングの正規表現周り

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

きっと正しくないレンタルサーバーの作り方 Vol.2 - ざっくりアーキテクト

はじめに

前回の記事からだいぶ時間が経ちましたが、第2回めです(・∀・)。
今回は全体的なアーキテクトについて記述していこうと思います(・∀・)。

※注意!
この一連の記事で紹介するコードは動作の概念を説明するものでありセキュリティーなどは意識していません(・∀・)。

実際に運用するシステムなどに使用しないでください(・∀・)。
(そのまま使うひともいないと思いますが)

また、私も記事を書きながら開発をしていくので「後になってみたら最初の方の記事間違えてたー」なんて事は起きそうです(・∀・)。

ご了承ください(・∀・)。

目次

全体の構成

基本的に単一のサーバーで動作しますが、後から複数台構成に移行しやすいような構成を目指します(・∀・)。

アカウント管理

rentaserve.com では
- 管理者アカウント(以降「アカウント」と表記)
- ユーザーアカウント(以降「ユーザー」と表記)
の二種類のアカウントがあります(・∀・)。

どちらもレンタルサーバーを使用するユーザーが使用するアカウントですが、ちょっと意味合いが異なります(・∀・)。

例えば

https://mao.rentaserve.com

というサイトを作ったとしたら、maoアカウント と呼びます(・∀・)。

そして

taro@mao.rentaserve.com
jiro@mao.rentaserve.com
saburo@mao.rentaserve.com

のようなメール / FTP アカウントを作成した場合、taro や jiro を ユーザー と呼びます(・∀・)。

rentaserve.com では

  • アカウントを LDAP(ActiveDirectory)で管理
    • ActiveDirectory には Samba 4 AD を使用
  • ユーザーを mariadb で管理

することにします(・∀・)。
LDAPを採用することにより、サーバーを複数台にした時にアカウントを一元管理しやすいためです(・∀・)。
各サーバーにドメインコントローラーを設置して連携させることで、他のサーバーにアカウントを追加しても他のサーバーに反映されるように出来るためです(・∀・)。

また、アカウントのパスワードの秘匿性が高いという利点もあります(・∀・)。
(ActiveDirectory はたとえ管理者でもパスワードのハッシュ値さえ見れない仕様のため)

rentaserve.com システムの本体からは LDAP 関数を使用してアカウントの操作をします(・∀・)。

ウェブサーバー

単純に Apache2.4 を使用します(・∀・)。
php-fpm を使用して PHP スクリプトを実行できるようにします(・∀・)。

特に Apache 固有の機能などは使用しないため、別に nginx 等でも構いません(・∀・)。

サブドメイン毎に設定ファイルを作成し、php-fpm の Unix ソケット経由で PHP を実行します(・∀・)。

セキュリティー確保のため、ユーザー毎に Unix ソケットを作成して PHP を実行します(・∀・)。

PHP

前述した通り、7.3 を使用します(・∀・)。

php-fpm 用の設定を各ユーザーごとに作成して、そこにユーザー固有の設定を書くことによって xdebug などを設定可能にします(・∀・)。
また、
- exec など使い方によってはセキュリティー事故の危険性が高い関数を禁止する
- LDAP系の関数を使用禁止にする
- chroot を使用してホームディレクトリ以下までしかアクセスできないようにする
- php-fpm の実行ユーザーをアカウントユーザーと同一にする(www-data などで実行しない)
のような細工をしておきます(・∀・)。

FTP

ProFTPd を使用します(・∀・)。
ユーザーは mariadb で管理し、ドメイン単位でアクセス先のディレクトリを制限します(・∀・)。
ドメイン単位でのアクセス制限とは、例えば mao というアカウントを作成したとして、そのアカウントが独自ドメイン「example.com」を取得した場合、

というような意味です(・∀・)。
別に一緒くたにしたり混在したりした管理も可能ですが、基本的に
「そのドメインのひとがそのドメインのウェブサイトを管理するだろうなー」
という方針のため、このような仕様にします(・∀・)。

また、FTPS(FTP over SSL)でのアクセスのみ可能とします(・∀・)。
生の FTP はセキュリティー的にアレなので使用させない方向で考えています(・∀・)。

独自ドメインを設定したユーザーの場合、FTPクライアントで証明書関連の警告が出るかも

メール

Postfix / Dovecot を使用します(・∀・)。

これもユーザー管理に mariadb を使用します(・∀・)。
メールは IMAP のみサポートし、POP3 はサポートしないことにします(・∀・)。
(特に理由はない)

こちらも IMAPS / SMTPS といった TLS 通信のみ許可する方向で考えています(・∀・)。

ファイヤーウォール

ufw などを使用します(・∀・)。
あんまり考えてはいません(・∀・)w

サーバー管理

今回は考えません(・∀・)。

興味ある人は Zabbix などを入れてみるとかどうでしょう(・∀・)。
(投げやり)

外部公開DNS

今回は PowerDNS を使用します(・∀・)。
(bind は使いにくい)

PowerDNS 採用の理由は
- レコードを mariadb で管理できる
- Debian/GNU Linux 10 標準のパッケージなので apt で簡単に使用できる
ためです(・∀・)。

ドメイン

とりあえず お名前.com を使用しています(・∀・)。
他のレジストラでも良いと思いますが、よく分かっていません(・∀・)w

独自ドメインについては rentaserve.com を権威DNSサーバーとして動作させ、ネームサーバーに rentaserve.com(ns1.rentaserve.com とか ns2.rentaserve.com とか作って)アクセスさせることで実現させるつもりです(・∀・)。

独自ドメインの設定時にネームサーバーが *.rentaserve.com ではない場合に弾くような仕組みです(・∀・)。

証明書

みんな大好き Let's Encrypt を使用します(・∀・)。

*.rentaserve.com のワイルドカードドメインを取得するためちょっと細工が必要そうです(・∀・)。

最後に

つらつらと長くなりましたが、次回から実際にサーバーを構築していこうと思います(・∀・)。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rangeパーティションを削除する方法

パーティションしたテーブルを戻したい事はほとんどないような気がしますが、
たまたまやる機会があったので備忘録的な物として残しておきます。

まずは、普通に作ったパーティションを全て削除しようと思いました。
ALTER TABLE table_name DROP PARTITION p201905, p201908,
p201911, p202002, p202005, p202008, p202011, p202102, p202105, p202108, p202111;

Cannot remove all partitions, use DROP TABLE instead というエラーが出て駄目でした。

解決方法
ALTER TABLE table_name REMOVE PARTITIONING;

件数に寄りますがかなり時間が掛かります。

create unique index unique_index_activities_id on activities (id) ;
ALTER TABLE table_name DROP PRIMARY KEY;
ALTER TABLE table_name ADD PRIMARY KEY (id) ;
DROP INDEX uniq_index_activities_id_and_created_at ON table_name

あとは、プライマリーキーを削除するために、一旦ユニークキーを作成して、プライマリーキーを削除して
IDで再度作り直して、Rangeパーティションのユニークインデックスキーを削除しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQL 結合を使用したらエラーが出た

目的

  • テーブル結合を使用したら「Not unique table/alias」というエラーが出たので解決方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提情報

  • 架空のマッチングアプリのユーザ情報を格納しているテーブルを用いて説明を行う。
  • ユーザ情報としてマッチングしたいユーザID、ユーザ名、マッチングしたい異性の最低年齢、マッチングしたい異性の最高年齢、本人の年齢を格納しているusersテーブルに年齢のレンジを格納しているage_rangesテーブルを結合したい。
  • DB、テーブル、カラム、各データの挿入は下記のSQL、またはコマンドを上から実行して行った。

    1. MySQLのログイン

      $ mysql -u root -p
      
    2. DB.テーブル、カラム、各データの挿入は下記のSQLを実行した。

      create database join_test;
      use join_test;
      create table users(id int, name varchar(255), matching_age_min int, matching_age_max int, age int);
      create table age_ranges( id int, age_range varchar(255));
      insert into users(id, name, matching_age_min, matching_age_max, age) values (1, 'miriwo', 2, 5, 2);
      insert into age_ranges(id, age_range) value(1, '下限なし');
      
    3. SQLshow tables;を実行したときに下記のように表示される。

      +---------------------+
      | Tables_in_join_test |
      +---------------------+
      | age_ranges          |
      | users               |
      +---------------------+
      2 rows in set (0.00 sec)
      
    4. SQLselect * from users;を実行したときに下記のように表示される。

      +------+--------+------------------+------------------+------+
      | id   | name   | matching_age_min | matching_age_max | age  |
      +------+--------+------------------+------------------+------+
      |    1 | miriwo |                2 |                5 |    2 |
      +------+--------+------------------+------------------+------+
      1 row in set (0.00 sec)
      
    5. SQLselect * from age_ranges;を実行したときに下記のように表示される。

      +------+--------------+
      | id   | age_range    |
      +------+--------------+
      |    1 | 下限なし     |
      |    2 | 20代         |
      |    3 | 30代         |
      |    4 | 40代         |
      |    5 | 50代         |
      |    6 | 上限なし     |
      +------+--------------+
      6 rows in set (0.00 sec)
      

      テーブルの情報

  • usersテーブル

    カラム名 データ型
    id int
    name varchar
    matching_age_min int
    matching_age_max int
    age int
  • age_rangesテーブル

    カラム名 データ型
    id int
    age_range varchar

テーブル結合を使って表示したいもの

  • usersテーブルとage_rangesテーブルを結合して下記のように表示したい。

    id name matching_age_min matching_age_max age
    1 miriwo 20代 50代 20代

実行したSQLと結果

  • テーブル結合の基本に乗っ取り下記のSQLを実行した。

    select users.id, users.name, age_ranges.age_range, age_ranges.age_range, age_ranges.age_range from users 
    join age_ranges 
    on users.matching_age_min = age_ranges.id 
    join age_ranges 
    on users.matching_age_max = age_ranges.id 
    join age_ranges 
    on users.age = age_ranges.id 
    where users.id = 1;
    
  • 下記のエラーが出力された。

    ERROR 1066 (42000): Not unique table/alias: 'age_ranges'
    

解決策

  • 下記のようなSQLを実行すれば問題は解決する、SQL記載後に簡単に説明をする。

    select users.id, users.name, age_ranges_min.age_range, age_ranges_max.age_range, age_ranges_user.age_range from users 
    join age_ranges as age_ranges_min
    on users.matching_age_min = age_ranges_min.id 
    join age_ranges as age_ranges_max
    on users.matching_age_max = age_ranges_max.id 
    join age_ranges as age_ranges_user
    on users.age = age_ranges_user.id 
    where users.id = 1;
    
  • ポイントは「join句で結合を指定するときにasを使って別名を指定する」である。

  • 下記に上記のSQLのjoin句の一部を記載する。

    join age_ranges as age_ranges_min
    on users.matching_age_min = age_ranges_min.id
    
  • 上記ではjoin句で結合先テーブルを指定するときにasを用いて別名を与えている。直後のon句では結合条件のテーブル名を別名を用いて指定している。

  • またselect句でも「join句で指定した別名.カラム名」と指定する事により、select句で出力するカラムの重複を防いでいる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker(docker-compose) + MySQL でのパスワード入力時のエラー / ERROR 1045 (28000): Access denied for user

RailsをAPIモードで Docker + MySQL を使用してWebアプリ開発をしています。

環境構築時に、
docker-compose up して dbにログインしようとすると
ERROR 1045 (28000): Access denied for user
ログインができないエラーに遭遇したので、私が解決できた方法を記載しておきます!

解決法

私の場合は、docker-compose.ymlで

docker-compose.yml
db-data:/var/lib/mysql:cached

このようにしてdbデータをローカルに保存してマウントしていたのですが、
ここに試しに作っていた、いくつかのvolumeが残っていたため、
そちらの環境変数が優先され、現在のパスワードが反映されていないという問題でした。

MySQL公式に

既にデータベースを格納しているデータディレクトリとともにコンテナを起動した場合は、下記の変数はどれも影響を及ぼさないことに留意してください。

とあるようです。
詳しくは こちら の記事をご覧ください。

つまり、
マウント先のローカルのdbを一旦削除して、再度作り直せばOKということです。

手順

docker-compose down
docker volume ls
docker volume rm (lsで表示されたvolume名をスペース区切りで並べる)
docker-compose up

で、作り直して再度ログインすると、問題なくログインできました!!

上記の手順で、
docker volume rm時にもしかすると、削除できない場合があるかもしれません。
その時は、

docker system prune
docker volume rm ○○

とするとうまくいくかも知れません!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker-ComposeでDockerのベース環境を簡単構築![Sample:MySQL]

目的

docker-composeによって、簡単にDocker環境構築をしてみよう、と言う記事です。

自分が環境を先日作った際に、あまりyml文内の意味やオプション、コマンドって結局どういう意味やねん、、、とプチパニックになったのでまとめ直したものになります。そのため知識、かつ環境の構築がこの記事のみで完結できるような構成を目指しました。

あまりMarkDown記法になれていないこともあるため、少し拙い文章になるかと思いますが、読んでいただけると幸いです。

サクッと作るぞ!チートシート

この記事をみてサクッと環境作りたいわ、って人のために
まず実行するためだけの手順を完全に説明を省いて紹介していきます。
今回はサンプルのためにMySQL環境を作成しますが、
柔軟に変更できるようにコメントにて、可変できる部分は特記しています。

構成

構成は以下の通りに作成しました。

├── docker
│   └── db
│       ├── data
│       ├── mysql-config.cnf
│       └── init
│           ├── 001-create-tables.sql
│           └── init-database.sh
└── docker-compose.yml

ソースコード

1. docker-compose.yml

アプリケーションを構成するサービスをdocker-compose.ymlに書きます。

今回はMySQLにあたりますね。

これを使用するコードディレクトリの一番親のディレクトリに生成します。

# docker-compose.yml
version: '3'
services:
  # (例1)ここに使用するサービスを書きます
  mysql:
    # image > コンテナ実行時の元になるイメージです。使うサービスのリポジトリ名だったりを書きます。
    image: mysql:latest
    # volume > 仮想環境上でのファイルをパスを指定してアクセスできるようにします。パスはこのymlファイルがあるディレクトリ基準です
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/mysql-config.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/init:/docker-entrypoint-initdb.d
        # environment > 環境変数を指定します。Compose実行時に指定されるものにあたります。
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: database
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'

    # (例2)goを使う場合であれば以下のようになります。細かい詳細は解説にて説明、参照します。
    go:
        build:
          context: ./go/
          target: dev
        volumes:
          - ./go/src:/go/src:cached
        working_dir: /go/src
        ports:
          - 8080:8080
        tty: true
        env_file:
          - ./go/.env

MySQL以外の環境を構築する場合は3. へ飛んでください。

2. MySQL環境用のソースコード

MySQLの場合に初期DBを用意する必要があるので、設定ファイル(mysql-config.cnf)と初期テーブル(001-create-tables.sql)のコードを書き、それを実行するコード(init-database.sh)を書きます。

### mysql-config.cnf ###
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4
--- 001-create-tables.sql ---
---- drop ----
DROP TABLE IF EXISTS `first_table`;

---- create ----
create table IF not exists `first_table`
(
 `id`               INT(20) AUTO_INCREMENT,
 `name`             VARCHAR(20) NOT NULL,
 `created_at`       Datetime DEFAULT NULL,
 `updated_at`       Datetime DEFAULT NULL,
    PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
### init-database.sh ###
#!/usr/bin/env bash
#wait for the MySQL Server to come up
#sleep 90s

#run the setup script to create the DB and the schema in the DB
mysql -u docker -pdocker test_database < "/docker-entrypoint-initdb.d/001-create-tables.sql"

3. Docker起動

imageを指定していない場合(例2)、ビルドを行い、イメージを構築します。この時に参照されるのがよく巷で聞くDockerfileです。今回はMySQL環境を爆速で仕上げるため省略しますが、解説にて説明します。

今回は使用するイメージ(image)を

# docker-compose.yml
version: '3'
services:
  # (例1)ここに使用するサービスを書きます
  mysql:
    # image > コンテナ実行時の元になるイメージです。使うサービスのリポジトリ名だったりを書きます。
    image: mysql:latest

ここで指定しているので、コンテナを作成し起動するところからで大丈夫です。

$ docker-compose up
Creating network "backend_default" with the default driver
Creating backend_mysql_1 ... done
Creating backend_go_1    ... done
...

上記コマンドで起動できたら、同じ階層にいる別ターミナルで以下のコマンドによって起動を確認してみてください。

$ docker-compose ps   
     Name                   Command             State           Ports         
------------------------------------------------------------------------------
backend_go_1      bash                          Up      0.0.0.0:8080->8080/tcp
backend_mysql_1   docker-entrypoint.sh mysqld   Up      3306/tcp, 33060/tcp

ここで起動し、サービス名が表示されていれば構築完了です!早いですね。

最後に以下コマンドによってコンテナとそれに関わるネットワークを停止します。

$ docker-compose down
Stopping backend_mysql_1 ... done
Stopping backend_go_1    ... done
Removing backend_mysql_1 ... done
Removing backend_go_1    ... done
Removing network backend_default

下記コマンドを叩いたターミナルが

$ docker-compose up
Creating network "backend_default" with the default driver
Creating backend_mysql_1 ... done
Creating backend_go_1    ... done
...
mysql_1  | 2020-11-30T14:42:11.612252Z 0 [System] [MY-013172] [Server] Received SHUTDOWN from user <via user signal>. Shutting down mysqld (Version: 8.0.22).
go_1     | root@f1d2304a041d:/go/src# exit
backend_go_1 exited with code 0
mysql_1  | 2020-11-30T14:42:12.324000Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.22)  MySQL Community Server - GPL.
backend_mysql_1 exited with code 0

上記のようにexitできていれば、無事に停止しています。

これでDocker-composeによって仮想環境が構築できました。

4. MySQLでsample-tableを生成

仮想環境に入り、init-database.shを叩きます。

コンテナ内で起動中にコマンドを叩く場合は以下を参考にしてください。

# コンテナ起動
$ docker-compose up
...

> 別ターミナルでdocker環境に入り、コマンド実行
# 権限付与 > ここでyml中に書いたvolumesで、パスを指定できる、と言うわけです
$ docker-compose exec mysql bash -c "chmod 0775 docker-entrypoint-initdb.d/init-database.sh"
# init-database.shを叩く
$ docker-compose exec mysql bash -c "./docker-entrypoint-initdb.d/init-database.sh"

解説

最初にアジェンダ書いてた時は、ここにがっつり解説書いていこうと思ったのですが、思ったより上で説明してしまった感が否めないです。書くことなくなってきた。。。まずいぞ。。。

そもそもdocker-composeって??

複数のコンテナを、簡易に管理し、実行するDockerアプリケーションのためのツールの1つです。
コマンドを1つ実行するだけで、docker-compose.ymlに定義した設定に基づいて環境を構築してくれるので、本当にいろんな使い方ができます!!、、(らしいですと言うのが本心。お恥ずかしい。)

ymlで(よく)使用するオプション

image:

コンテナを実行する時に元となるイメージを指定します。
自分でDockerfileを書いて実行する場合は、次のbuildオプションを使用します。
imageで使用する場合はビルドを行わず、$ docker-compose up からで大丈夫です。

build:

コンテナを実行する時に参照するDockerfileを指定します。外部imageではなく、自分でDockerfileを制作して使う場合はよく以下の構成で使用します。

自分でいちからDockerfileを書く、よりはリポジトリをクローンしてきて修正する、という扱い方が多いと思います。

├── docker
├── Dockerfile
└── docker-compose.yml
# docker-compose.yml
version: '3'
services:
  hoge:
    # 同階層のため相対パスで指定
    build: .

ここで自分でDockerfileを書いた場合はイメージを構築、すなわちビルドする必要があるので、初期起動前に以下コマンドを叩きます。

$ docker-compose build
db uses an image, skipping
Building go ......

使用するcontextの指定や、dockerfile生成に関してはリファレンスも参照することをお勧めします。
https://docs.docker.jp/compose/compose-file.html#context

environment:

主に仮想環境上で用いる環境変数を指定します。
配列、もしくはDictionaryで定義できるので、$ docker-compose execで実行する際にアクセスできるようにAPI鍵や今回のようにMySQLのホストの値を記載します。

env_file:

上記のenvironmentでは隠したい情報(自分はAPI鍵などはenv_fileを使用して.gitignoreで隠す、という形をとることが多いです)の場合は、これで環境変数を記載したファイルを指定します。

# docker-compose.yml
version: '3'
services:
    go:
        env_file:
          - ./go/.env

なお.envファイルは以下のように、変数 = 値の形を取ります。

# /go/.env
DB_USER=user
DB_PASSWORD=password
DB_HOST=mysql:3306
DB_DATABASE=database
DB_SOCKET=tcp

volumes:

自分のローカルファイルを環境上のファイルに割り当てるように指定することができます。

なお、実行手順にもコメントで書いた通り、ymlファイルがあるディレクトリを基準として相対パスは使用できます。

# docker-compose.yml
version: '3'
services:
  mysql:
    volumes:
      # ローカルファイル:仮想環境上のパス となります
      - ./mysql/data:/var/lib/mysql
      - ./mysql/mysql-config.cnf:/etc/mysql/conf.d/my.cnf
      - ./mysql/init:/docker-entrypoint-initdb.d

ports:

ホスト側とコンテナ側、両者のポートを指定することができます。
(なお、コンテナのみの指定も可能)

# docker-compose.yml
version: '3'
services:
    go:
        ports:
        # ローカル側:仮想環境側 という感じです
          - 8080:8080
        env_file:
          - ./go/.env

docker-composeで(よく)使うコマンド

説明するコマンドを以下に書いておきます。このほうがササッと使いやすいですよね。

$ docker-compose build
$ docker-compose up
$ docker-compose down
$ docker-compose ps
$ docker-compose start
$ docker-compose stop

$ docker-compose build

image:を指定せずにbuild:を使用する際に使います。これによりサービスをビルドします。
上記にも書いた通り、image:を指定しない場合は、以下の$ docker-compose upの前にビルドする必要があります。

頻度の高いオプション
--no-cache 構築時にイメージのキャッシュを使わない

$ docker-compose up

仮想環境の立ち上げを行います。
実際はコンテナを構築、作成し、起動、アタッチまでを全てこのコマンドで行ってくれます。

なお、-d をつけることによって、バックグラウンドで実行されるため、コンテナは起動し続ける状態を確保できます。CIなどを回す際にはこのオプションを使うと便利です。

頻度の高いオプション
-d バックグラウンドでコンテナを実行

$ docker-compose down

起動している仮想環境を停止し、$ docker-compose up によって立ち上げたコンテナとネットワークを削除します。

頻度の高いオプション
--rmi all 全イメージを削除

$ docker-compose ps

起動しているコンテナの一覧を表示できます。

$ docker-compose exec

起動している環境に対して、任意のコマンドを実行することができます。
なので、基本的に環境上でプロンプトを動かしたりする際はこれを使用します。

# exec後の部分でどのコンテナを叩くか指定します
$ docker-compose exec mysql bash -c "./docker-entrypoint-initdb.d/init-database.sh"

# 以下コマンドのように扱えば、直接シェルを叩くこともできますが、volumesにてデフォルトの階層を指定する必要があります
$ docker-compose exec hogehoge /bin/bash
頻度の高いオプション
-u 指定されたユーザによりコマンドを実行

$ docker-compose start

既存のコンテナをサービスとして起動します。
$ docker-compose upすれば起動するので、$ docker-compose downではなく(停止後コンテナを削除するため)、$ docker-compose stop などで停止した場合に再び起動する時に使用します。

$ docker-compose stop

稼働中のコンテナを停止しますが、$ docker-compose down とは違い、削除しません。 上記の$ docker-compose start コマンドで、再起動できます。

まとめ

docker-composeによる仮想環境構築でした。
運用だと、docker-compose.ymlに複数コンテナを組み合わせて一挙に立てて使用することになることがおおいですね。

初日ということでしたが、自分が実装していた時にメモしていたことなどを噛み砕いて書いていくうちにどんどんボリュームが増しちゃいました。(笑)

ただ、語彙的にもとっつかみやすい文章になってるんじゃないでしょうか、、と思っています、、、(そうでなければ僕の語彙力の問題ですね、反省します)

皆様のますますのご活躍の少しでもお手伝いになれば、ということを書き締めたいと思います。

また質問等あればコメントにてご指摘などよろしくお願いします。

参考資料

  • docker-compose オプション / リファレンス

https://docs.docker.jp/compose/compose-file.html

  • docker-compose コマンド / リファレンス

https://docs.docker.jp/compose/reference/index.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む