マルチバイト語族をなめてんのか
ごっつり文字化けするんですよね…。
Dockerの公式MySQLはcaracter-set-server
がデフォルトのまんまのlatin1
です。
そりゃASCII
文化圏ならutf8
だろうがlatin1
だろうが文字化けしないだろうさ…。
でもマルチバイト語族は常に、つ、ね、に、文字化けの恐怖に晒されるのですよ。
そこで今回はDockerの公式MySQLでちゃんと日本語を扱えるようにするための方法をまとめてみました。
記事ではMySQL5.7(mysql/5.7/Dockerfile)を使っています。
文字化けについてちょっとした説明
設定変更の方法を紹介する前にちょっとした説明です。
MySQLで文字化けの原因となるのは
caracter-set-server
がデフォルトのlatin1
のままになっている為というのがあります。
日本語を扱うにはマルチバイトの文字セットが必要ですが、現状だとutf8
、utf8mb4
を使うことになるかと思います
たとえば日本語の漢字で「泡」はutf8
で
E6B3A1
というコードになります。3バイトで表現されていることがわかるかと思います。
utf8
という解釈をするとこのコードは「泡」ということになるのですが、1バイト毎での解釈もできてしまう余地があります。
つまりこういう風に
E6 B3 A1
1バイトの文字が3つとも解釈できるのです。latin1
ではこれらが
æ ³ ¡
と解釈されます。
つまり
「泡」は「泡」になってしまうのです。
こんな風にutf8
で書いた文字列がlatin1
として解釈される「コードの取り違え」があると以下の様な壮絶な文字化けが発生してしまうのです。
脂肪(ã—ã¼ã†ã€è‹±:fat)ã¨ã¯è„‚(ã‚ã¶ã‚‰ï¼‰ã¨ã‚‚ã„ã„ã€å‹•æ¤ç‰©ã«å«ã¾ã‚Œã‚
なにやらこんな文字化け見たことありますよね。
ちなみにアルファベット・アラビア数字・一般的な記号部分が文字化けしないのは、それらを定義しているASCII
という文字セットがほとんどの文字セットに含まれているからです。(つまり同じコードを使っているので文字化けしようがない)
latin1
もASCII
を含んでいるのでアルファベット・アラビア数字・一般的な記号部分は文字化けしません。
次は具体的な方法に行きましょう。
その1 コンテナを作る時に設定ファイルを変更する方法
caracter-set-server
を恒久的に設定するにはmy.cnf
を編集する必要があります。
この方法は直接手動でmy.cnf
を修正する方法です。設定を確認できるので、MySQLコンテナの設定を把握したい場合などにいいかもしれません。
my.cnfを得る
まずは型になるmy.cnf
を手に入れましょう。これはコンテナから引っこ抜くのが早いです。
「my.cnf
をコピーする用」のコンテナを作ります。
$ docker run -d --name test -e MYSQL_ROOT_PASSWORD=goma -e MYSQL_DATABASE=testdb mysql
docker cp
コマンドでコンテナからmy.cnf
をコピーします。
$ docker cp test:/etc/mysql/my.cnf my.cnf
エディタで開いてみましょう。
$ vim my.cnf
MySQLコンテナに作られたmy.cnf
は以下のような記述になっています。
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# The MySQL Community Server configuration file.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
#log-error = /var/log/mysql/error.log
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# * IMPORTANT: Additional settings that can override those from this file!
# The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/
Dockerfile上での処理で
skip-host-cache
skip-name-resolve
がawk
での処理によって付け足され
#bind-address = 127.0.0.1
#log-error = /var/log/mysql/error.log
がsed
での処理でコメントアウトされています。
caracter-set-server書き足し
さてutf8
をデフォルトにするためにcaracter-set-server
の設定を書き加えます。
[mysqld]の直下に
caracter-set-server = utf8
もしくは
caracter-set-server = utf8mb4
を書き加えます。以下のようになっていれば大丈夫です。
~~~~
~~~~
[mysqld]
caracter-set-server = utf8
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
~~~~
~~~~
または
~~~~
~~~~
[mysqld]
caracter-set-server = utf8mb4
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
~~~~
~~~~
さようならコンテナ
いま修正したmy.cnf
を適用させた新しいコンテナをつくるので、「my.cnf
をコピーする用」に作ったコンテナは削除して構いません。さようなら。
$ docker stop test
$ docker rm test
データボリュームの準備
さきほど修正したmy.cnf
を任意のディレクトリに入れておきます( 例では/hoge/mycnf/
)。そのディレクトリをデータボリュームとしてコンテナと共有することでmy.cnf
をコンテナの中に入れます。
公式MySQLでインストールされているMySQLは/etc/mysql/conf.d/
に*.cnf
形式のファイルが入っているとその設定を優先するようになっています。
# * IMPORTANT: Additional settings that can override those from this file!
# The files must end with '.cnf', otherwise they'll be ignored.
/etc/mysql/my.cnf
を上書きするのでも問題はないのですが、今回は/etc/mysql/conf.d/
につっこんでいくスタイルでいきます。
ですのでmy.cnf
という名前じゃなくても大丈夫です。拡張子さえcnf
にしていれば管理しやすい様に名前を変えても問題ありません。
「使う用のコンテナ」を作る
以上のことを踏まえて「使う用のコンテナ」は以下のようにして作ります。
docker run -d --name usedb -v /hoge/mycnf/:/etc/mysql/conf.d/ -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=db mysql
これで修正したmycnf
の設定が生きたコンテナが出来ました。
がっちり文字化けしていたのが…
なんということでしょう…!!
わ〜い。
その2 Dockerfileを修正する硬派な方法
コンテナからmy.cnf
をコピーするなど軟弱なことはやっておれん、という硬派な方にはDockerfileの修正がオススメです。
まずはGitHubからDockerfileをcloneしましょう。
$ git clone git@github.com:docker-library/mysql.git
Dockerfileを開きます。
$ vim mysql/5.7/Dockerfile
いろいろ処理が書かれていますが、34行目あたりに以下のような記述があります。この部分がmy.cnf
を修正している箇所になります。
# comment out a few problematic configuration values
# don't reverse lookup hostnames, they are usually another container
RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf \
&& echo 'skip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \
&& mv /tmp/my.cnf /etc/mysql/my.cnf
さらに詳しくみると以下の部分が、「[mysqld]の直下にskip-name-resolveおよびskip-host-cacheを挿入する処理」を行っている箇所です。
&& echo 'skip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \
ここを修正すればcaracter-set-server
を挿入することができるかと思います。
以下の様にしてみてください。
&& echo 'caracter-set-server = utf8\nskip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \
または
&& echo 'caracter-set-server = utf8mb4\nskip-host-cache\nskip-name-resolve' | awk '{ print } $1 == "[mysqld]" && c == 0 { c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \
さて修正したDockerfileをbuild
しましょう。
$ docker build -t hogemysql hoge/mysql/5.7/
build
したイメージでコンテナを作ります。
docker run -d --name hogedb -e MYSQL_ROOT_PASSWORD=goma -e MYSQL_DATABASE=db hoge/mysql
my.cnf
をコピーして確認してみると…
~~~
~~~
[mysqld]
caracter-set-server = utf8
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.
~~~
~~~
または
~~~
~~~
[mysqld]
caracter-set-server = utf8mb4
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.
~~~
~~~
ちゃんとcaracter-set-server = utf8
やcaracter-set-server = utf8mb4
が挿入されていますね。
これでもじばけしないさいきょうのまいえすきゅーえるいめーじがてにはいったぞ!!
補足・ハマりポイント
もしかしたら以下のようなエラーがでてdocker build
が失敗するかもしれません。
E: Version '5.7.11-1debian8' for 'mysql-server' was not found
これはdebianリポジトリに指定したバージョンが無いときに起こるエラーのようです。この記事を書いている時ちょうど5.7.12-1debian8
がリリースされたようで、5.7.11-1debian8
がリポジトリからなくなりました。
Dockefileのメンテを待っても良いのですが、直接書き換えても大丈夫かと思います。今回はDockerfileの以下の様に書き換えました。
ENV MYSQL_VERSION 5.7.12-1debian8
あとで確認したらGitHubの方もすでに修正されていました。
ちなみにこの件に関してはissuesがあります。どうもバージョンを取り違えないように、その都度指定する方法にしているみたいですね。
version 5.7 fails to apt-get install mysql-server=’5.7.10-1debian8′ #139
もし上記のようなエラーがでたら確認してみてください。
まとめ
- 公式のMySQLイメージは
caracter-set-server
がデフォルトのlatin1
になっている - 文字化けを起こす原因になるので
caracter-set-server
はutf8
かutf8mb4
にしたい - コンテナの
my.cnf
を書き換えることで文字化け防ぐことができる - データボリュームで
my.cnf
を突っ込む方法とDokcefileを修正する硬派な方法がある - これで文字化けしないぜ!!