以下内容均来自个人笔记并重新梳理,如有错误欢迎指正!
如果对您有帮助,烦请点赞、关注、转发!如果您有其他想要了解的,欢迎私信联系我~
近期,笔者正推进公司 MySQL 适配 ARM 64 架构工作,由于一直使用 Docker Hub 上的官方镜像,所以第一时间在 Hub 上检索,却发现官方只为 MySQL 8.0 以上版本提供 ARM 64 镜像。
为避免 MySQL 版本变动带来的研发改造成本,笔者最终决定针对 MySQL 5.7.40 版本自行构建 ARM 64 镜像,以下为完整过程。
过程回顾
alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage"dfimage -sV=1.36 mysql:5.7.40 > /root/mysql_rebuild/Dockerfile
这里需要说明的是,该 Dockerfile 中是通过 yum 安装 mysql 服务,经过实测其指定的 yum 源也未提供 5.7.40 版本的 ARM 64 rpm文件,因此还需要使用源码自行编译生成 rpm,并修改 Dockerfile 中的安装逻辑。
附 yum 源地址:https://repo.mysql.com/yum
2、编译源码生成 rpm
yum install -y cmake time libaio-devel ncurses-devel numactl-devel openssl-devel zlib-devel \cyrus-sasl-devel openldap-devel perl-Env gcc gcc-c++ make rpm-build autoconf perl-JSON
wget https://repo.mysql.com/yum/mysql-5.7-community/docker/el/7/SRPMS/mysql-community-minimal-5.7.40-1.el7.src.rpmrpm -ivh mysql-community-minimal-5.7.40-1.el7.src.rpm
cd /root/rpmbuild/SOURCES/tar -xzvf mysql-5.7.40.tar.gz && rm -rf mysql-5.7.40.tar.gzvim mysq1-5.7.40/sql/mysqld.cc,添加 #includesed -i "s#-O3#-O1#g" mysql-5.7.40/cmake/build_configurations/compiler_options.cmaketar -czvf mysql-5.7.40.tar.gz mysql-5.7.40 --remove-files
rpmbuild -bb /root/rpmbuild/SPECS/mysql.spec
生成的 rpm 文件位于 /root/rpmbuild/RPMS/aarch64 目录下
cd /root/mysql_rebuild && tree目录结构如下,部分文件的详细内容见附录:Dockerfileaarch64├── mysql-community-minimal-debuginfo-5.7.40-1.el7.aarch64.rpm└── mysql-community-server-minimal-5.7.40-1.el7.aarch64.rpmdocker-entrypoint.shgosugosu.ascmy.cnfmysql-shell-8.0.12-1.el7.aarch64.rpm=linux/arm64 -t mysql:5.7.40-armtest -f Dockerfile . --no-cache
FROM oraclelinux:7-slimCMD ["/bin/bash"]RUN set -eux; groupadd --system --gid 999 mysql; useradd --system --uid 999 --gid 999 --home-dir /var/lib/mysql --no-create-home mysqlENV GOSU_VERSION=1.14# 此处提前将 gosu 原文件下载好,缩短构建时间# wget -O gosu.asc https://github.com/tianon/gosu/releases/download/1.14/gosu-arm64.asc# wget -O gosu https://github.com/tianon/gosu/releases/download/1.14/gosu-arm64COPY gosu.asc gosu /usr/local/binRUN set -eux; export GNUPGHOME="$(mktemp -d)"; gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; chmod +x /usr/local/bin/gosu; gosu --version; gosu nobody true; yum install -y --setopt=skip_missing_names_on_install=False oracle-epel-release-el7; yum install -y --setopt=skip_missing_names_on_install=False bzip2 gzip openssl xz zstd; yum clean allENV MYSQL_MAJOR=5.7ENV MYSQL_VERSION=5.7.40-1.el7# 此处提前将 rpm 原文件下载好,缩短构建时间COPY aarch64 mysql-shell-8.0.12-1.el7.aarch64.rpm /tmpCOPY my.cnf /etc/my.cnfRUN set -eux; yum install -y libaio numactl; cd /tmp && rpm -ivh --force mysql-community-minimal-debuginfo-5.7.40-1.el7.aarch64.rpm mysql-community-server-minimal-5.7.40-1.el7.aarch64.rpm; yum clean all; mkdir -p /etc/mysql/conf.d; mkdir -p /etc/mysql/mysql.conf.d; find /etc/my.cnf /etc/mysql/ -name '*.cnf' -print0 | xargs -0 grep -lZE '^(bind-address|log)' | xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/'; mkdir -p /var/lib/mysql /var/run/mysqld; chown mysql:mysql /var/lib/mysql /var/run/mysqld; chmod 1777 /var/lib/mysql /var/run/mysqld; mkdir /docker-entrypoint-initdb.d; mysqld --version; mysql --versionENV MYSQL_SHELL_VERSION=8.0.31-1.el7RUN set -eux; rpm -ivh /tmp/mysql-shell-8.0.12-1.el7.aarch64.rpm; rm -rf /tmp;mkdir /tmp; chmod -R 777 /tmp; yum clean all; mysqlsh --version# 🔔 /var/lib/mysql 需要添加引号,否则运行时报错 mount destination [/var/lib/mysql] not absolute: unknownVOLUME ["/var/lib/mysql"]COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.shRUN ln -s /usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compatENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 3306 33060CMD ["mysqld"]
2、docker-entrypoint.sh
set -eo pipefailshopt -s nullglob# logging functionsmysql_log() {local type="$1"; shift# accept argument string or stdinlocal text="$*"; if [ "$#" -eq 0 ]; then text="$(cat)"; filocal dt; dt="$(date --rfc-3339=seconds)"printf '%s [%s] [Entrypoint]: %s\n' "$dt" "$type" "$text"}mysql_note() {mysql_log Note "$@"}mysql_warn() {mysql_log Warn "$@" >&2}mysql_error() {mysql_log ERROR "$@" >&2exit 1}# usage: file_env VAR [DEFAULT]# ie: file_env 'XYZ_DB_PASSWORD' 'example'# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)file_env() {local var="$1"local fileVar="${var}_FILE"local def="${2:-}"if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; thenmysql_error "Both $var and $fileVar are set (but are exclusive)"filocal val="$def"if [ "${!var:-}" ]; thenval="${!var}"elif [ "${!fileVar:-}" ]; thenval="$(< "${!fileVar}")"fiexport "$var"="$val"unset "$fileVar"}# check to see if this file is being run or sourced from another script_is_sourced() {# https://unix.stackexchange.com/a/215279[ "${#FUNCNAME[@]}" -ge 2 ] \&& [ "${FUNCNAME[0]}" = '_is_sourced' ] \&& [ "${FUNCNAME[1]}" = 'source' ]}# usage: docker_process_init_files [file [file [...]]]# ie: docker_process_init_files /always-initdb.d/*# process initializer files, based on file extensionsdocker_process_init_files() {# mysql here for backwards compatibility "${mysql[@]}"mysql=( docker_process_sql )echolocal ffor f; docase "$f" in*.sh)# https://github.com/docker-library/postgres/issues/450#issuecomment-393167936# https://github.com/docker-library/postgres/pull/452if [ -x "$f" ]; thenmysql_note "$0: running $f""$f"elsemysql_note "$0: sourcing $f". "$f"fi;;*.sql) mysql_note "$0: running $f"; docker_process_sql < "$f"; echo ;;*.sql.bz2) mysql_note "$0: running $f"; bunzip2 -c "$f" | docker_process_sql; echo ;;*.sql.gz) mysql_note "$0: running $f"; gunzip -c "$f" | docker_process_sql; echo ;;*.sql.xz) mysql_note "$0: running $f"; xzcat "$f" | docker_process_sql; echo ;;*.sql.zst) mysql_note "$0: running $f"; zstd -dc "$f" | docker_process_sql; echo ;;*) mysql_warn "$0: ignoring $f" ;;esacechodone}# arguments necessary to run "mysqld --verbose --help" successfully (used for testing configuration validity and for extracting default/configured values)_verboseHelpArgs=(--verbose --help--log-bin-index="$(mktemp -u)" # https://github.com/docker-library/mysql/issues/136)mysql_check_config() {local toRun=( "$@" "${_verboseHelpArgs[@]}" ) errorsif ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; thenmysql_error $'mysqld failed while attempting to check config\n\tcommand was: '"${toRun[*]}"$'\n\t'"$errors"fi}# Fetch value from server config# We use mysqld --verbose --help instead of my_print_defaults because the# latter only show values present in config files, and not server defaultsmysql_get_config() {local conf="$1"; shift"$@" "${_verboseHelpArgs[@]}" 2>/dev/null \| awk -v conf="$conf" '$1 == conf && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }'# match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)"}# Ensure that the package default socket can also be used# since rpm packages are compiled with a different socket location# and "mysqlsh --mysql" doesn't read the [client] config# related to https://github.com/docker-library/mysql/issues/829mysql_socket_fix() {local defaultSocketdefaultSocket="$(mysql_get_config 'socket' mysqld --no-defaults)"if [ "$defaultSocket" != "$SOCKET" ]; thenln -sfTv "$SOCKET" "$defaultSocket" || :fi}# Do a temporary startup of the MySQL server, for init purposesdocker_temp_server_start() {if [ "${MYSQL_MAJOR}" = '5.7' ]; then"$@" --skip-networking --default-time-zone=SYSTEM --socket="${SOCKET}" &mysql_note "Waiting for server startup"local ifor i in {30..0}; do# only use the root password if the database has already been initialized# so that it won't try to fill in a password file when it hasn't been set yetextraArgs=()if [ -z "$DATABASE_ALREADY_EXISTS" ]; thenextraArgs+=( '--dont-use-mysql-root-password' )fiif docker_process_sql "${extraArgs[@]}" --database=mysql <<<'SELECT 1' &> /dev/null; thenbreakfisleep 1doneif [ "$i" = 0 ]; thenmysql_error "Unable to start server."fielse# For 5.7+ the server is ready for use as soon as startup command unblocksif ! "$@" --daemonize --skip-networking --default-time-zone=SYSTEM --socket="${SOCKET}"; thenmysql_error "Unable to start server."fifi}# Stop the server. When using a local socket file mysqladmin will block until# the shutdown is complete.docker_temp_server_stop() {if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; thenmysql_error "Unable to shut down server."fi}# Verify that the minimally required password settings are set for new databases.docker_verify_minimum_env() {if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; thenmysql_error <<EOFDatabase is uninitialized and password option is not specifiedYou need to specify one of the following as an environment variable:- MYSQL_ROOT_PASSWORD- MYSQL_ALLOW_EMPTY_PASSWORD- MYSQL_RANDOM_ROOT_PASSWORDEOFfi# This will prevent the CREATE USER from failing (and thus exiting with a half-initialized database)if [ "$MYSQL_USER" = 'root' ]; thenmysql_error <<EOFMYSQL_USER="root", MYSQL_USER and MYSQL_PASSWORD are for configuring a regular user and cannot be used for the root userRemove MYSQL_USER="root" and use one of the following to control the root user password:- MYSQL_ROOT_PASSWORD- MYSQL_ALLOW_EMPTY_PASSWORD- MYSQL_RANDOM_ROOT_PASSWORDEOFfi# warn when missing one of MYSQL_USER or MYSQL_PASSWORDif [ -n "$MYSQL_USER" ] && [ -z "$MYSQL_PASSWORD" ]; thenmysql_warn 'MYSQL_USER specified, but missing MYSQL_PASSWORD; MYSQL_USER will not be created'elif [ -z "$MYSQL_USER" ] && [ -n "$MYSQL_PASSWORD" ]; thenmysql_warn 'MYSQL_PASSWORD specified, but missing MYSQL_USER; MYSQL_PASSWORD will be ignored'fi}# creates folders for the database# also ensures permission for user mysql of run as rootdocker_create_db_directories() {local user; user="$(id -u)"local -A dirs=( ["$DATADIR"]=1 )local dirdir="$(dirname "$SOCKET")"dirs["$dir"]=1# "datadir" and "socket" are already handled above (since they were already queried previously)local conffor conf in \general-log-file \keyring_file_data \pid-file \secure-file-priv \slow-query-log-file \; dodir="$(mysql_get_config "$conf" "$@")"# skip empty valuesif [ -z "$dir" ] || [ "$dir" = 'NULL' ]; thencontinueficase "$conf" insecure-file-priv)# already points at a directory;;*)# other config options point at a file, but we need the directorydir="$(dirname "$dir")";;esacdirs["$dir"]=1donemkdir -p "${!dirs[@]}"if [ "$user" = "0" ]; then# this will cause less disk access than `chown -R`find "${!dirs[@]}" \! -user mysql -exec chown --no-dereference mysql '{}' \;fi}# initializes the database directorydocker_init_database_dir() {mysql_note "Initializing database files""$@" --initialize-insecure --default-time-zone=SYSTEMmysql_note "Database files initialized"}# Loads various settings that are used elsewhere in the script# This should be called after mysql_check_config, but before any other functionsdocker_setup_env() {# Get configdeclare -g DATADIR SOCKETDATADIR="$(mysql_get_config 'datadir' "$@")"SOCKET="$(mysql_get_config 'socket' "$@")"# Initialize values that might be stored in a filefile_env 'MYSQL_ROOT_HOST' '%'file_env 'MYSQL_DATABASE'file_env 'MYSQL_USER'file_env 'MYSQL_PASSWORD'file_env 'MYSQL_ROOT_PASSWORD'declare -g DATABASE_ALREADY_EXISTSif [ -d "$DATADIR/mysql" ]; thenDATABASE_ALREADY_EXISTS='true'fi}# Execute sql script, passed via stdin# usage: docker_process_sql [--dont-use-mysql-root-password] [mysql-cli-args]# ie: docker_process_sql --database=mydb <<<'INSERT ...'# ie: docker_process_sql --dont-use-mysql-root-password --database=mydb <my-file.sqldocker_process_sql() {passfileArgs=()if [ '--dont-use-mysql-root-password' = "$1" ]; thenpassfileArgs+=( "$1" )shiftfi# args sent in can override this db, since they will be later in the commandif [ -n "$MYSQL_DATABASE" ]; thenset -- --database="$MYSQL_DATABASE" "$@"fimysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" --comments "$@"}# Initializes database with timezone info and root password, plus optional extra db/userdocker_setup_db() {# Load timezone info into databaseif [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then# sed is for https://bugs.mysql.com/bug.php?id=20545mysql_tzinfo_to_sql /usr/share/zoneinfo \| sed 's/Local time zone must be set--see zic manual page/FCTY/' \| docker_process_sql --dont-use-mysql-root-password --database=mysql# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is not set yetfi# Generate random root passwordif [ -n "$MYSQL_RANDOM_ROOT_PASSWORD" ]; thenMYSQL_ROOT_PASSWORD="$(openssl rand -base64 24)"; export MYSQL_ROOT_PASSWORDmysql_note "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"fi# Sets root password and creates root users for non-localhost hostslocal rootCreate=# default root to listen for connections from anywhereif [ -n "$MYSQL_ROOT_HOST" ] && [ "$MYSQL_ROOT_HOST" != 'localhost' ]; then# no, we don't care if read finds a terminating character in this heredoc# https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151read -r -d '' rootCreate <<EOSQL || trueCREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;EOSQLfilocal passwordSet=# no, we don't care if read finds a terminating character in this heredoc (see above)read -r -d '' passwordSet <<EOSQL || trueALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;EOSQL# tell docker_process_sql to not use MYSQL_ROOT_PASSWORD since it is just now being setdocker_process_sql --dont-use-mysql-root-password --database=mysql <<EOSQL-- What's done in this file shouldn't be replicated-- or products like mysql-fabric won't workSET @@SESSION.SQL_LOG_BIN=0;${passwordSet}GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;FLUSH PRIVILEGES ;${rootCreate}DROP DATABASE IF EXISTS test ;EOSQL# Creates a custom database and user if specifiedif [ -n "$MYSQL_DATABASE" ]; thenmysql_note "Creating database ${MYSQL_DATABASE}"docker_process_sql --database=mysql <<<"CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;"fiif [ -n "$MYSQL_USER" ] && [ -n "$MYSQL_PASSWORD" ]; thenmysql_note "Creating user ${MYSQL_USER}"docker_process_sql --database=mysql <<<"CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;"if [ -n "$MYSQL_DATABASE" ]; thenmysql_note "Giving user ${MYSQL_USER} access to schema ${MYSQL_DATABASE}"docker_process_sql --database=mysql <<<"GRANT ALL ON \`${MYSQL_DATABASE//_/\\_}\`.* TO '$MYSQL_USER'@'%' ;"fifi}_mysql_passfile() {# echo the password to the "file" the client uses# the client command will use process substitution to create a file on the fly# ie: --defaults-extra-file=<( _mysql_passfile )if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MYSQL_ROOT_PASSWORD" ]; thencat <<EOF[client]password="${MYSQL_ROOT_PASSWORD}"EOFfi}# Mark root user as expired so the password must be changed before anything# else can be done (only supported for 5.6+)mysql_expire_root_user() {if [ -n "$MYSQL_ONETIME_PASSWORD" ]; thendocker_process_sql --database=mysql <<EOSQLALTER USER 'root'@'%' PASSWORD EXPIRE;EOSQLfi}# check arguments for an option that would cause mysqld to stop# return true if there is one_mysql_want_help() {local argfor arg; docase "$arg" in-'?'|--help|--print-defaults|-V|--version)return 0;;esacdonereturn 1}_main() {# if command starts with an option, prepend mysqldif [ "${1:0:1}" = '-' ]; thenset -- mysqld "$@"fi# skip setup if they aren't running mysqld or want an option that stops mysqldif [ "$1" = 'mysqld' ] && ! _mysql_want_help "$@"; thenmysql_note "Entrypoint script for MySQL Server ${MYSQL_VERSION} started."# 解决Kylin V10兼容性问题ulimit -n 1048576 && ulimit -a >/dev/null && ulimit -nmysql_check_config "$@"# Load various environment variablesdocker_setup_env "$@"docker_create_db_directories "$@"# If container is started as root user, restart as dedicated mysql userif [ "$(id -u)" = "0" ]; thenmysql_note "Switching to dedicated user 'mysql'"exec gosu mysql "$BASH_SOURCE" "$@"fi# there's no database, so it needs to be initializedif [ -z "$DATABASE_ALREADY_EXISTS" ]; thendocker_verify_minimum_env# check dir permissions to reduce likelihood of half-initialized databasels /docker-entrypoint-initdb.d/ > /dev/nulldocker_init_database_dir "$@"mysql_note "Starting temporary server"docker_temp_server_start "$@"mysql_note "Temporary server started."mysql_socket_fixdocker_setup_dbdocker_process_init_files /docker-entrypoint-initdb.d/*mysql_expire_root_usermysql_note "Stopping temporary server"docker_temp_server_stopmysql_note "Temporary server stopped"echomysql_note "MySQL init process done. Ready for start up."echoelsemysql_socket_fixfifiexec "$@"}# If we are sourced from elsewhere, don't perform any further actionsif ! _is_sourced; then_main "$@"fi
3、my.cnf
# For advice on how to change settings please see# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html[mysqld]## Remove leading # and set to the amount of RAM for the most important data# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.# innodb_buffer_pool_size = 128M## Remove leading # to turn on a very important data integrity option: logging# changes to the binary log between backups.# log_bin## Remove leading # to set options mainly useful for reporting servers.# The server defaults are faster for transactions and fast SELECTs.# Adjust sizes as needed, experiment to find the optimal values.# join_buffer_size = 128M# sort_buffer_size = 2M# read_rnd_buffer_size = 2Mskip-host-cacheskip-name-resolvedatadir=/var/lib/mysqlsocket=/var/run/mysqld/mysqld.socksecure-file-priv=/var/lib/mysql-filesuser=mysql# Disabling symbolic-links is recommended to prevent assorted security riskssymbolic-links=0#log-error=/var/log/mysqld.logpid-file=/var/run/mysqld/mysqld.pid[client]socket=/var/run/mysqld/mysqld.sock!includedir /etc/mysql/conf.d/!includedir /etc/mysql/mysql.conf.d/

