From 301111e07778c57799afbe002469f466241bf7d4 Mon Sep 17 00:00:00 2001 From: Honza Horak Date: Sun, 22 Oct 2017 14:26:13 +0200 Subject: [PATCH] Implement s2i and general extendability support This is very similar to the MariaDB: https://github.com/sclorg/mariadb-container/pull/45/ It adds extending support using [source-to-image](https://github.com/openshift/source-to-image). For example to build customized MySQL database image `my-mysql-centos7` with configuration in `~/image-configuration/` run: ``` $ s2i build ~/image-configuration/ centos/mysql-57-centos7 my-mysql-centos7 ``` The directory passed to `s2i build` can contain these directories: - `mysql-cfg/` - when starting the container, files from this directory will be used as a configuration for the `mysqld` daemon - `envsubst` command is run on this file to still allow customization of the image using environmental variables - `mysql-pre-init/` - shell scripts (`*.sh`) available in this directory are sourced before `mysqld` daemon is started - `mysql-init/` - shell scripts (`*.sh`) available in this directory are sourced when `mysqld` daemon is started locally - in this phase, use `${mysql_flags}` to connect to the locally running daemon, for example `mysql $mysql_flags < dump.sql` Variables that can be used in the scripts provided to s2i: - `$mysql_flags` -- arguments for the `mysql` tool that will connect to the locally running `mysqld` during initialization - `$MYSQL_RUNNING_AS_MASTER` -- variable defined when the container is run with `run-mysqld-master` command - `$MYSQL_RUNNING_AS_SLAVE` -- variable defined when the container is run with `run-mysqld-slave` command - `$MYSQL_DATADIR_FIRST_INIT` -- variable defined when the container was initialized from the empty data dir During `s2i build` all provided files are copied into `/opt/app-root/src` directory into the resulting image. If some configuration files are present in the destination directory, files with the same name are overwritten. Also only one file with the same name can be used for customization and user provided files are preferred over default files in `/usr/share/container-scripts/mysql/`- so it is possible to overwrite them. Same configuration directory structure can be used to customize the image every time the image is started using `docker run`. The directory has to be mounted into `/opt/app-root/src/` in the image (`-v ./image-configuration/:/opt/app-root/src/`). This overwrites customization built into the image. --- 5.6/Dockerfile | 13 +- 5.6/Dockerfile.rhel7 | 8 +- .../share/container-scripts/mysql/README.md | 76 ++++++++- 5.6/s2i-common | 1 + 5.7/Dockerfile | 2 +- 5.7/Dockerfile.fedora | 6 +- 5.7/Dockerfile.rhel7 | 6 +- .../share/container-scripts/mysql/README.md | 76 ++++++++- 5.7/s2i-common | 1 + examples/extend-image/mysql-cfg/myconfig.cnf | 3 + examples/extend-image/mysql-data/init.sql | 4 + .../mysql-init/80-add-arbitrary-users.sh | 17 ++ .../extend-image/mysql-init/90-init-db.sh | 12 ++ .../80-check-arbitrary-users.sh | 10 ++ .../mysql-certs/server-cert-selfsigned.pem | 20 +++ .../mysql-certs/server-key.pem | 28 +++ examples/self-signed-ssl/mysql-cfg/ssl.cnf | 4 + root-common/etc/my.cnf | 4 +- root-common/usr/bin/run-mysqld | 20 +-- root-common/usr/bin/run-mysqld-master | 19 +-- root-common/usr/bin/run-mysqld-slave | 16 +- root-common/usr/libexec/container-setup | 3 +- root-common/usr/libexec/fix-permissions | 6 + .../{my-paas.cnf.template => cnf/40-paas.cnf} | 0 .../50-my-tuning.cnf} | 0 .../share/container-scripts/mysql/common.sh | 54 ++++++ .../mysql/init/50-passwd-change.sh | 48 ++++++ .../container-scripts/mysql/passwd-change.sh | 37 ---- .../20-validate-variables.sh} | 4 +- .../25-validate-replication-variables.sh} | 5 +- .../mysql/pre-init/30-base-config.sh | 3 + .../mysql/pre-init/60-replication-config.sh | 17 ++ .../mysql/pre-init/70-s2i-config.sh | 6 + .../mysql/{ => pre-init}/my-base.cnf.template | 0 .../{ => pre-init}/my-master.cnf.template | 0 .../{ => pre-init}/my-repl-gtid.cnf.template | 0 .../{ => pre-init}/my-slave.cnf.template | 0 s2i-common/bin/assemble | 13 ++ s2i-common/bin/run | 1 + s2i-common/bin/usage | 8 + test/run | 161 ++++++++++++++++-- test/test-app | 1 + 42 files changed, 601 insertions(+), 112 deletions(-) create mode 120000 5.6/s2i-common create mode 120000 5.7/s2i-common create mode 100644 examples/extend-image/mysql-cfg/myconfig.cnf create mode 100644 examples/extend-image/mysql-data/init.sql create mode 100644 examples/extend-image/mysql-init/80-add-arbitrary-users.sh create mode 100644 examples/extend-image/mysql-init/90-init-db.sh create mode 100644 examples/extend-image/mysql-pre-init/80-check-arbitrary-users.sh create mode 100644 examples/self-signed-ssl/mysql-certs/server-cert-selfsigned.pem create mode 100644 examples/self-signed-ssl/mysql-certs/server-key.pem create mode 100644 examples/self-signed-ssl/mysql-cfg/ssl.cnf create mode 100755 root-common/usr/libexec/fix-permissions rename root-common/usr/share/container-scripts/mysql/{my-paas.cnf.template => cnf/40-paas.cnf} (100%) rename root-common/usr/share/container-scripts/mysql/{my-tuning.cnf.template => cnf/50-my-tuning.cnf} (100%) create mode 100644 root-common/usr/share/container-scripts/mysql/init/50-passwd-change.sh delete mode 100644 root-common/usr/share/container-scripts/mysql/passwd-change.sh rename root-common/usr/share/container-scripts/mysql/{validate-variables.sh => pre-init/20-validate-variables.sh} (98%) rename root-common/usr/share/container-scripts/mysql/{validate-replication-variables.sh => pre-init/25-validate-replication-variables.sh} (87%) create mode 100644 root-common/usr/share/container-scripts/mysql/pre-init/30-base-config.sh create mode 100644 root-common/usr/share/container-scripts/mysql/pre-init/60-replication-config.sh create mode 100644 root-common/usr/share/container-scripts/mysql/pre-init/70-s2i-config.sh rename root-common/usr/share/container-scripts/mysql/{ => pre-init}/my-base.cnf.template (100%) rename root-common/usr/share/container-scripts/mysql/{ => pre-init}/my-master.cnf.template (100%) rename root-common/usr/share/container-scripts/mysql/{ => pre-init}/my-repl-gtid.cnf.template (100%) rename root-common/usr/share/container-scripts/mysql/{ => pre-init}/my-slave.cnf.template (100%) create mode 100755 s2i-common/bin/assemble create mode 120000 s2i-common/bin/run create mode 100755 s2i-common/bin/usage create mode 120000 test/test-app diff --git a/5.6/Dockerfile b/5.6/Dockerfile index da2cca7f..04046447 100644 --- a/5.6/Dockerfile +++ b/5.6/Dockerfile @@ -1,4 +1,4 @@ -FROM centos:centos7 +FROM centos/s2i-core-centos7 # MySQL image for OpenShift. # @@ -11,6 +11,7 @@ FROM centos:centos7 # * $MYSQL_ROOT_PASSWORD (Optional) - Password for the 'root' MySQL account ENV MYSQL_VERSION=5.6 \ + APP_DATA=/opt/app-root/src \ HOME=/var/lib/mysql ENV SUMMARY="MySQL 5.6 SQL database server" \ @@ -35,9 +36,10 @@ EXPOSE 3306 # This image must forever use UID 27 for mysql user so our volumes are # safe in the future. This should *never* change, the last test is there # to make sure of that. -RUN yum install -y centos-release-scl && \ - INSTALL_PKGS="tar rsync gettext hostname bind-utils rh-mysql56" && \ - yum -y --setopt=tsflags=nodocs install $INSTALL_PKGS && \ +RUN yum install -y yum-utils && \ + yum install -y centos-release-scl && \ + INSTALL_PKGS="rsync tar gettext hostname bind-utils groff-base shadow-utils rh-mysql56" && \ + yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ mkdir -p /var/lib/mysql/data && chown -R mysql.0 /var/lib/mysql && \ @@ -56,12 +58,13 @@ ENV BASH_ENV=${CONTAINER_SCRIPTS_PATH}/scl_enable \ PROMPT_COMMAND=". ${CONTAINER_SCRIPTS_PATH}/scl_enable" COPY 5.6/root-common / +COPY 5.6/s2i-common/bin/ $STI_SCRIPTS_PATH COPY 5.6/root / # this is needed due to issues with squash # when this directory gets rm'd by the container-setup # script. -RUN rm -rf /etc/my.cnf.d/* +RUN rm -rf /etc/my.cnf.d/* RUN /usr/libexec/container-setup VOLUME ["/var/lib/mysql/data"] diff --git a/5.6/Dockerfile.rhel7 b/5.6/Dockerfile.rhel7 index bc1a687d..f307aca2 100644 --- a/5.6/Dockerfile.rhel7 +++ b/5.6/Dockerfile.rhel7 @@ -1,4 +1,4 @@ -FROM rhel7 +FROM rhscl/s2i-core-rhel7 # MySQL image for OpenShift. # @@ -11,6 +11,7 @@ FROM rhel7 # * $MYSQL_ROOT_PASSWORD (Optional) - Password for the 'root' MySQL account ENV MYSQL_VERSION=5.6 \ + APP_DATA=/opt/app-root/src \ HOME=/var/lib/mysql ENV SUMMARY="MySQL 5.6 SQL database server" \ @@ -43,7 +44,7 @@ RUN yum repolist > /dev/null && \ yum-config-manager --enable rhel-7-server-rpms && \ yum-config-manager --enable rhel-7-server-optional-rpms && \ yum-config-manager --enable rhel-server-rhscl-7-rpms && \ - INSTALL_PKGS="rsync tar gettext hostname bind-utils rh-mysql56" && \ + INSTALL_PKGS="rsync tar gettext hostname bind-utils groff-base shadow-utils rh-mysql56" && \ yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ @@ -63,12 +64,13 @@ ENV BASH_ENV=${CONTAINER_SCRIPTS_PATH}/scl_enable \ PROMPT_COMMAND=". ${CONTAINER_SCRIPTS_PATH}/scl_enable" COPY 5.6/root-common / +COPY 5.6/s2i-common/bin/ $STI_SCRIPTS_PATH COPY 5.6/root / # this is needed due to issues with squash # when this directory gets rm'd by the container-setup # script. -RUN rm -rf /etc/my.cnf.d/* +RUN rm -rf /etc/my.cnf.d/* RUN /usr/libexec/container-setup VOLUME ["/var/lib/mysql/data"] diff --git a/5.6/root/usr/share/container-scripts/mysql/README.md b/5.6/root/usr/share/container-scripts/mysql/README.md index 5ad40772..0af81880 100644 --- a/5.6/root/usr/share/container-scripts/mysql/README.md +++ b/5.6/root/usr/share/container-scripts/mysql/README.md @@ -44,7 +44,6 @@ or if it was already present, `mysqld` is executed and will run as PID 1. You ca stop the detached container by running `docker stop mysql_database`. - Environment variables and volumes --------------------------------- @@ -142,6 +141,81 @@ location is `/etc/my.cnf` but you can change it to `/etc/mysql/my.cnf` by settin `MYSQL_DEFAULTS_FILE=/etc/mysql/my.cnf` +Extending image +--------------- +This image can be extended using [source-to-image](https://github.com/openshift/source-to-image). + +For example, to build a customized MariaDB database image `my-mysql-rhel7` +with a configuration in `~/image-configuration/` run: + +``` +$ s2i build ~/image-configuration/ rhscl/mysql-56-rhel7 my-mysql-rhel7 +``` + +The directory passed to `s2i build` can contain these directories: + +`mysql-cfg/` + When starting the container, files from this directory will be used as + a configuration for the `mysqld` daemon. + `envsubst` command is run on this file to still allow customization of + the image using environmental variables + +`mysql-pre-init/` + Shell scripts (`*.sh`) available in this directory are sourced before + `mysqld` daemon is started. + +`mysql-init/` + Shell scripts (`*.sh`) available in this directory are sourced when + `mysqld` daemon is started locally. In this phase, use `${mysql_flags}` + to connect to the locally running daemon, for example `mysql $mysql_flags < dump.sql` + +Variables that can be used in the scripts provided to s2i: + +`$mysql_flags` + arguments for the `mysql` tool that will connect to the locally running `mysqld` during initialization + +`$MYSQL_RUNNING_AS_MASTER` + variable defined when the container is run with `run-mysqld-master` command + +`$MYSQL_RUNNING_AS_SLAVE` + variable defined when the container is run with `run-mysqld-slave` command + +`$MYSQL_DATADIR_FIRST_INIT` + variable defined when the container was initialized from the empty data dir + +During `s2i build` all provided files are copied into `/opt/app-root/src` +directory into the resulting image. If some configuration files are present +in the destination directory, files with the same name are overwritten. +Also only one file with the same name can be used for customization and user +provided files are preferred over default files in +`/usr/share/container-scripts/mysql/`- so it is possible to overwrite them. + +Same configuration directory structure can be used to customize the image +every time the image is started using `docker run`. The directory has to be +mounted into `/opt/app-root/src/` in the image +(`-v ./image-configuration/:/opt/app-root/src/`). +This overwrites customization built into the image. + + +Securing the connection with SSL +-------------------------------- +In order to secure the connection with SSL, use the extending feature described +above. In particular, put the SSL certificates into a separate directory: + + sslapp/mysql-certs/server-cert-selfsigned.pem + sslapp/mysql-certs/server-key.pem + +And then put a separate configuration file into mysql-cfg: + + $> cat sslapp/mysql-cfg/ssl.cnf + [mysqld] + ssl-key=${APP_DATA}/mysql-certs/server-key.pem + ssl-cert=${APP_DATA}/mysql-certs/server-cert-selfsigned.pem + +Such a directory `sslapp` can then be mounted into the container with -v, +or a new container image can be built using s2i. + + Changing the replication binlog_format -------------------------------------- Some applications may wish to use `row` binlog_formats (for example, those built diff --git a/5.6/s2i-common b/5.6/s2i-common new file mode 120000 index 00000000..49d966b0 --- /dev/null +++ b/5.6/s2i-common @@ -0,0 +1 @@ +../s2i-common/ \ No newline at end of file diff --git a/5.7/Dockerfile b/5.7/Dockerfile index f8496411..c35d9c7a 100644 --- a/5.7/Dockerfile +++ b/5.7/Dockerfile @@ -38,7 +38,6 @@ EXPOSE 3306 # to make sure of that. RUN yum install -y yum-utils && \ yum install -y centos-release-scl && \ - yum-config-manager --enable centos-sclo-rh-testing && \ INSTALL_PKGS="rsync tar gettext hostname bind-utils groff-base shadow-utils rh-mysql57" && \ yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ @@ -59,6 +58,7 @@ ENV BASH_ENV=${CONTAINER_SCRIPTS_PATH}/scl_enable \ PROMPT_COMMAND=". ${CONTAINER_SCRIPTS_PATH}/scl_enable" COPY 5.7/root-common / +COPY 5.7/s2i-common/bin/ $STI_SCRIPTS_PATH COPY 5.7/root / # this is needed due to issues with squash diff --git a/5.7/Dockerfile.fedora b/5.7/Dockerfile.fedora index c14a119c..2c4ff39a 100644 --- a/5.7/Dockerfile.fedora +++ b/5.7/Dockerfile.fedora @@ -1,4 +1,4 @@ -FROM registry.fedoraproject.org/fedora:26 +FROM registry.fedoraproject.org/f26/s2i-core:latest # MySQL image for OpenShift. # @@ -11,6 +11,7 @@ FROM registry.fedoraproject.org/fedora:26 # * $MYSQL_ROOT_PASSWORD (Optional) - Password for the 'root' MySQL account ENV MYSQL_VERSION=5.7 \ + APP_DATA=/opt/app-root/src \ HOME=/var/lib/mysql ENV SUMMARY="MySQL 5.7 SQL database server" \ @@ -47,7 +48,7 @@ RUN ln -s /usr/bin/python3 /usr/bin/python # This image must forever use UID 27 for mysql user so our volumes are # safe in the future. This should *never* change, the last test is there # to make sure of that. -RUN INSTALL_PKGS="rsync tar gettext hostname bind-utils community-mysql-server policycoreutils" && \ +RUN INSTALL_PKGS="rsync tar gettext hostname bind-utils groff-base shadow-utils community-mysql-server policycoreutils" && \ yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ @@ -59,6 +60,7 @@ ENV CONTAINER_SCRIPTS_PATH=/usr/share/container-scripts/mysql \ MYSQL_PREFIX=/usr COPY 5.7/root-common / +COPY 5.7/s2i-common/bin/ $STI_SCRIPTS_PATH COPY 5.7/root / # this is needed due to issues with squash diff --git a/5.7/Dockerfile.rhel7 b/5.7/Dockerfile.rhel7 index 27cb18d0..e25fdea8 100644 --- a/5.7/Dockerfile.rhel7 +++ b/5.7/Dockerfile.rhel7 @@ -1,4 +1,4 @@ -FROM rhel7 +FROM rhscl/s2i-core-rhel7 # MySQL image for OpenShift. # @@ -11,6 +11,7 @@ FROM rhel7 # * $MYSQL_ROOT_PASSWORD (Optional) - Password for the 'root' MySQL account ENV MYSQL_VERSION=5.7 \ + APP_DATA=/opt/app-root/src \ HOME=/var/lib/mysql ENV SUMMARY="MySQL 5.7 SQL database server" \ @@ -43,7 +44,7 @@ RUN yum repolist > /dev/null && \ yum-config-manager --enable rhel-7-server-rpms && \ yum-config-manager --enable rhel-7-server-optional-rpms && \ yum-config-manager --enable rhel-server-rhscl-7-rpms && \ - INSTALL_PKGS="rsync tar gettext hostname bind-utils rh-mysql57" && \ + INSTALL_PKGS="rsync tar gettext hostname bind-utils groff-base shadow-utils rh-mysql57" && \ yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ @@ -63,6 +64,7 @@ ENV BASH_ENV=${CONTAINER_SCRIPTS_PATH}/scl_enable \ PROMPT_COMMAND=". ${CONTAINER_SCRIPTS_PATH}/scl_enable" COPY 5.7/root-common / +COPY 5.7/s2i-common/bin/ $STI_SCRIPTS_PATH COPY 5.7/root / # this is needed due to issues with squash diff --git a/5.7/root/usr/share/container-scripts/mysql/README.md b/5.7/root/usr/share/container-scripts/mysql/README.md index 09bc8314..3315d3a4 100644 --- a/5.7/root/usr/share/container-scripts/mysql/README.md +++ b/5.7/root/usr/share/container-scripts/mysql/README.md @@ -44,7 +44,6 @@ or if it was already present, `mysqld` is executed and will run as PID 1. You ca stop the detached container by running `docker stop mysql_database`. - Environment variables and volumes --------------------------------- @@ -142,6 +141,81 @@ location is `/etc/my.cnf` but you can change it to `/etc/mysql/my.cnf` by settin `MYSQL_DEFAULTS_FILE=/etc/mysql/my.cnf` +Extending image +--------------- +This image can be extended using [source-to-image](https://github.com/openshift/source-to-image). + +For example, to build a customized MariaDB database image `my-mysql-rhel7` +with a configuration in `~/image-configuration/` run: + +``` +$ s2i build ~/image-configuration/ rhscl/mysql-57-rhel7 my-mysql-rhel7 +``` + +The directory passed to `s2i build` can contain these directories: + +`mysql-cfg/` + When starting the container, files from this directory will be used as + a configuration for the `mysqld` daemon. + `envsubst` command is run on this file to still allow customization of + the image using environmental variables + +`mysql-pre-init/` + Shell scripts (`*.sh`) available in this directory are sourced before + `mysqld` daemon is started. + +`mysql-init/` + Shell scripts (`*.sh`) available in this directory are sourced when + `mysqld` daemon is started locally. In this phase, use `${mysql_flags}` + to connect to the locally running daemon, for example `mysql $mysql_flags < dump.sql` + +Variables that can be used in the scripts provided to s2i: + +`$mysql_flags` + arguments for the `mysql` tool that will connect to the locally running `mysqld` during initialization + +`$MYSQL_RUNNING_AS_MASTER` + variable defined when the container is run with `run-mysqld-master` command + +`$MYSQL_RUNNING_AS_SLAVE` + variable defined when the container is run with `run-mysqld-slave` command + +`$MYSQL_DATADIR_FIRST_INIT` + variable defined when the container was initialized from the empty data dir + +During `s2i build` all provided files are copied into `/opt/app-root/src` +directory into the resulting image. If some configuration files are present +in the destination directory, files with the same name are overwritten. +Also only one file with the same name can be used for customization and user +provided files are preferred over default files in +`/usr/share/container-scripts/mysql/`- so it is possible to overwrite them. + +Same configuration directory structure can be used to customize the image +every time the image is started using `docker run`. The directory has to be +mounted into `/opt/app-root/src/` in the image +(`-v ./image-configuration/:/opt/app-root/src/`). +This overwrites customization built into the image. + + +Securing the connection with SSL +-------------------------------- +In order to secure the connection with SSL, use the extending feature described +above. In particular, put the SSL certificates into a separate directory: + + sslapp/mysql-certs/server-cert-selfsigned.pem + sslapp/mysql-certs/server-key.pem + +And then put a separate configuration file into mysql-cfg: + + $> cat sslapp/mysql-cfg/ssl.cnf + [mysqld] + ssl-key=${APP_DATA}/mysql-certs/server-key.pem + ssl-cert=${APP_DATA}/mysql-certs/server-cert-selfsigned.pem + +Such a directory `sslapp` can then be mounted into the container with -v, +or a new container image can be built using s2i. + + Changing the replication binlog_format -------------------------------------- Some applications may wish to use `row` binlog_formats (for example, those built diff --git a/5.7/s2i-common b/5.7/s2i-common new file mode 120000 index 00000000..49d966b0 --- /dev/null +++ b/5.7/s2i-common @@ -0,0 +1 @@ +../s2i-common/ \ No newline at end of file diff --git a/examples/extend-image/mysql-cfg/myconfig.cnf b/examples/extend-image/mysql-cfg/myconfig.cnf new file mode 100644 index 00000000..7764adf9 --- /dev/null +++ b/examples/extend-image/mysql-cfg/myconfig.cnf @@ -0,0 +1,3 @@ +[mysqld] +query-cache-limit=262144 + diff --git a/examples/extend-image/mysql-data/init.sql b/examples/extend-image/mysql-data/init.sql new file mode 100644 index 00000000..31599829 --- /dev/null +++ b/examples/extend-image/mysql-data/init.sql @@ -0,0 +1,4 @@ +CREATE TABLE products (id INTEGER, name VARCHAR(256), price FLOAT, variant INTEGER); +CREATE TABLE products_variant (id INTEGER, name VARCHAR(256)); +INSERT INTO products_variant (id, name) VALUES ('1', 'blue'), ('2', 'green'); + diff --git a/examples/extend-image/mysql-init/80-add-arbitrary-users.sh b/examples/extend-image/mysql-init/80-add-arbitrary-users.sh new file mode 100644 index 00000000..55ae2d29 --- /dev/null +++ b/examples/extend-image/mysql-init/80-add-arbitrary-users.sh @@ -0,0 +1,17 @@ +create_arbitrary_users() { + # Do not care what option is compulsory here, just create what is specified + log_info "Creating user specified by MYSQL_OPERATIONS_USER (${MYSQL_OPERATIONS_USER}) ..." +mysql $mysql_flags < /etc/my.cnf.d/base.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-paas.cnf.template > /etc/my.cnf.d/paas.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-tuning.cnf.template > /etc/my.cnf.d/tuning.cnf +# pre-init files +process_extending_files ${APP_DATA}/mysql-pre-init/ ${CONTAINER_SCRIPTS_PATH}/pre-init/ if [ ! -d "$MYSQL_DATADIR/mysql" ]; then initialize_database "$@" @@ -21,14 +17,8 @@ else start_local_mysql "$@" fi -if [ -f ${CONTAINER_SCRIPTS_PATH}/passwd-change.sh ]; then - log_info 'Setting passwords ...' - source ${CONTAINER_SCRIPTS_PATH}/passwd-change.sh -fi -if [ -f ${CONTAINER_SCRIPTS_PATH}/post-init.sh ]; then - log_info 'Sourcing post-init.sh ...' - source ${CONTAINER_SCRIPTS_PATH}/post-init.sh -fi +# init files +process_extending_files ${APP_DATA}/mysql-init/ ${CONTAINER_SCRIPTS_PATH}/init/ # Restart the MySQL server with public IP bindings shutdown_local_mysql diff --git a/root-common/usr/bin/run-mysqld-master b/root-common/usr/bin/run-mysqld-master index e282e2dd..ed332eaa 100755 --- a/root-common/usr/bin/run-mysqld-master +++ b/root-common/usr/bin/run-mysqld-master @@ -3,27 +3,22 @@ # This is an entrypoint that runs the MySQL server in the 'master' mode. # +export_vars=$(cgroup-limits); export $export_vars source ${CONTAINER_SCRIPTS_PATH}/common.sh set -eu export_setting_variables -[ -f ${CONTAINER_SCRIPTS_PATH}/validate-replication-variables.sh ] && source ${CONTAINER_SCRIPTS_PATH}/validate-replication-variables.sh -[ -f ${CONTAINER_SCRIPTS_PATH}/validate-variables.sh ] && source ${CONTAINER_SCRIPTS_PATH}/validate-variables.sh - log_volume_info $MYSQL_DATADIR +export MYSQL_RUNNING_AS_MASTER=1 + # The 'server-id' for master needs to be constant export MYSQL_SERVER_ID=1 log_info "The 'master' server-id is ${MYSQL_SERVER_ID}" -# Process the MySQL configuration files -log_info 'Processing MySQL configuration files ...' -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-base.cnf.template > /etc/my.cnf.d/base.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-paas.cnf.template > /etc/my.cnf.d/paas.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-master.cnf.template > /etc/my.cnf.d/master.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-repl-gtid.cnf.template > /etc/my.cnf.d/repl-gtid.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-tuning.cnf.template > /etc/my.cnf.d/tuning.cnf +# pre-init files +process_extending_files ${APP_DATA}/mysql-pre-init/ ${CONTAINER_SCRIPTS_PATH}/pre-init/ if [ ! -d "$MYSQL_DATADIR/mysql" ]; then initialize_database "$@" @@ -40,8 +35,8 @@ mysql $mysql_flags < /etc/my.cnf.d/base.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-paas.cnf.template > /etc/my.cnf.d/paas.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-slave.cnf.template > /etc/my.cnf.d/slave.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-repl-gtid.cnf.template > /etc/my.cnf.d/repl-gtid.cnf -envsubst < ${CONTAINER_SCRIPTS_PATH}/my-tuning.cnf.template > /etc/my.cnf.d/tuning.cnf +# pre-init files +process_extending_files ${APP_DATA}/mysql-pre-init/ ${CONTAINER_SCRIPTS_PATH}/pre-init/ if [ ! -e "${MYSQL_DATADIR}/mysql" ]; then # Initialize MySQL database and wait for the MySQL master to accept @@ -36,8 +30,8 @@ if [ ! -e "${MYSQL_DATADIR}/mysql" ]; then CHANGE MASTER TO MASTER_HOST='${MYSQL_MASTER_SERVICE_NAME}',MASTER_USER='${MYSQL_MASTER_USER}', MASTER_PASSWORD='${MYSQL_MASTER_PASSWORD}', MASTER_AUTO_POSITION = 1; EOSQL - log_info 'Sourcing post-init.sh ...' - [ -f ${CONTAINER_SCRIPTS_PATH}/post-init.sh ] && source ${CONTAINER_SCRIPTS_PATH}/post-init.sh + # init files + process_extending_files ${APP_DATA}/mysql-init/ ${CONTAINER_SCRIPTS_PATH}/init/ # Restart the MySQL server with public IP bindings shutdown_local_mysql diff --git a/root-common/usr/libexec/container-setup b/root-common/usr/libexec/container-setup index 29c6ed25..6160d4e8 100755 --- a/root-common/usr/libexec/container-setup +++ b/root-common/usr/libexec/container-setup @@ -54,5 +54,6 @@ restorecon -R /var/lib/mysql # arbitrary UID # When only specifying user, group is 0, that's why /var/lib/mysql must have # owner mysql.0; that allows to avoid a+rwx for this dir -chmod g+w -R /var/lib/mysql ${MYSQL_CONFIG_FILE}.d +/usr/libexec/fix-permissions /var/lib/mysql ${MYSQL_CONFIG_FILE}.d ${APP_DATA}/.. +usermod -a -G root mysql diff --git a/root-common/usr/libexec/fix-permissions b/root-common/usr/libexec/fix-permissions new file mode 100755 index 00000000..820e7181 --- /dev/null +++ b/root-common/usr/libexec/fix-permissions @@ -0,0 +1,6 @@ +#!/bin/sh +# Fix permissions on the given directory to allow group read/write of +# regular files and execute of directories. +find $@ -exec chown mysql:0 {} \; +find $@ -exec chmod g+rw {} \; +find $@ -type d -exec chmod g+x {} + diff --git a/root-common/usr/share/container-scripts/mysql/my-paas.cnf.template b/root-common/usr/share/container-scripts/mysql/cnf/40-paas.cnf similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-paas.cnf.template rename to root-common/usr/share/container-scripts/mysql/cnf/40-paas.cnf diff --git a/root-common/usr/share/container-scripts/mysql/my-tuning.cnf.template b/root-common/usr/share/container-scripts/mysql/cnf/50-my-tuning.cnf similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-tuning.cnf.template rename to root-common/usr/share/container-scripts/mysql/cnf/50-my-tuning.cnf diff --git a/root-common/usr/share/container-scripts/mysql/common.sh b/root-common/usr/share/container-scripts/mysql/common.sh index 51c62b6c..19f75b83 100644 --- a/root-common/usr/share/container-scripts/mysql/common.sh +++ b/root-common/usr/share/container-scripts/mysql/common.sh @@ -41,6 +41,9 @@ function export_setting_variables() { fi } +# this stores whether the database was initialized from empty datadir +export MYSQL_DATADIR_FIRST_INIT=false + # Be paranoid and stricter than we should be. # https://dev.mysql.com/doc/refman/en/identifiers.html mysql_identifier_regex='^[a-zA-Z0-9_]+$' @@ -139,6 +142,9 @@ mysql $mysql_flags < sourcing $filename ..." + # Custom file is prefered + if [ -f $custom_dir/$filename ]; then + source $custom_dir/$filename + else + source $default_dir/$filename + fi + done <<<"$(get_matched_files "$custom_dir" "$default_dir" '*.sh' | sort -u)" +} + +# process extending config files in $1 and $2 directories +# - expand variables in *.cnf and copy the files into /etc/my.cnf.d directory +# (if there are files with same name source only file from $1) +function process_extending_config_files() { + local custom_dir default_dir + custom_dir=$1 + default_dir=$2 + + while read filename ; do + echo "=> sourcing $filename ..." + # Custom file is prefered + if [ -f $custom_dir/$filename ]; then + envsubst < $custom_dir/$filename > /etc/my.cnf.d/$filename + else + envsubst < $default_dir/$filename > /etc/my.cnf.d/$filename + fi + done <<<"$(get_matched_files "$custom_dir" "$default_dir" '*.cnf' | sort -u)" +} diff --git a/root-common/usr/share/container-scripts/mysql/init/50-passwd-change.sh b/root-common/usr/share/container-scripts/mysql/init/50-passwd-change.sh new file mode 100644 index 00000000..2514f2dc --- /dev/null +++ b/root-common/usr/share/container-scripts/mysql/init/50-passwd-change.sh @@ -0,0 +1,48 @@ +password_change() { + log_info 'Setting passwords ...' + + # Set the password for MySQL user and root everytime this container is started. + # This allows to change the password by editing the deployment configuration. + if [[ -v MYSQL_USER && -v MYSQL_PASSWORD ]]; then +mysql $mysql_flags < "5.6" ] ; then +mysql $mysql_flags < "5.6" ] ; then +mysql $mysql_flags < "5.6" ]] ; then - mysql $mysql_flags < /etc/my.cnf.d/base.cnf + diff --git a/root-common/usr/share/container-scripts/mysql/pre-init/60-replication-config.sh b/root-common/usr/share/container-scripts/mysql/pre-init/60-replication-config.sh new file mode 100644 index 00000000..a923476f --- /dev/null +++ b/root-common/usr/share/container-scripts/mysql/pre-init/60-replication-config.sh @@ -0,0 +1,17 @@ +# mysqld configuration for replication scenarios + +if [ -v MYSQL_RUNNING_AS_MASTER ] || [ -v MYSQL_RUNNING_AS_SLAVE ] ; then + log_info 'Processing basic MySQL configuration for replication (master and slave) files ...' + envsubst < ${CONTAINER_SCRIPTS_PATH}/pre-init/my-repl-gtid.cnf.template > /etc/my.cnf.d/repl-gtid.cnf +fi + +if [ -v MYSQL_RUNNING_AS_MASTER ] ; then + log_info 'Processing basic MySQL configuration for replication (master only) files ...' + envsubst < ${CONTAINER_SCRIPTS_PATH}/pre-init/my-master.cnf.template > /etc/my.cnf.d/master.cnf +fi + +if [ -v MYSQL_RUNNING_AS_SLAVE ] ; then + log_info 'Processing basic MySQL configuration for replication (slave only) files ...' + envsubst < ${CONTAINER_SCRIPTS_PATH}/pre-init/my-slave.cnf.template > /etc/my.cnf.d/slave.cnf +fi + diff --git a/root-common/usr/share/container-scripts/mysql/pre-init/70-s2i-config.sh b/root-common/usr/share/container-scripts/mysql/pre-init/70-s2i-config.sh new file mode 100644 index 00000000..7a8ae5af --- /dev/null +++ b/root-common/usr/share/container-scripts/mysql/pre-init/70-s2i-config.sh @@ -0,0 +1,6 @@ +# additional arbitrary mysqld configuration provided by user using s2i + +log_info 'Processing additional arbitrary MySQL configuration provided by s2i ...' + +process_extending_config_files ${APP_DATA}/mysql-cfg/ ${CONTAINER_SCRIPTS_PATH}/cnf/ + diff --git a/root-common/usr/share/container-scripts/mysql/my-base.cnf.template b/root-common/usr/share/container-scripts/mysql/pre-init/my-base.cnf.template similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-base.cnf.template rename to root-common/usr/share/container-scripts/mysql/pre-init/my-base.cnf.template diff --git a/root-common/usr/share/container-scripts/mysql/my-master.cnf.template b/root-common/usr/share/container-scripts/mysql/pre-init/my-master.cnf.template similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-master.cnf.template rename to root-common/usr/share/container-scripts/mysql/pre-init/my-master.cnf.template diff --git a/root-common/usr/share/container-scripts/mysql/my-repl-gtid.cnf.template b/root-common/usr/share/container-scripts/mysql/pre-init/my-repl-gtid.cnf.template similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-repl-gtid.cnf.template rename to root-common/usr/share/container-scripts/mysql/pre-init/my-repl-gtid.cnf.template diff --git a/root-common/usr/share/container-scripts/mysql/my-slave.cnf.template b/root-common/usr/share/container-scripts/mysql/pre-init/my-slave.cnf.template similarity index 100% rename from root-common/usr/share/container-scripts/mysql/my-slave.cnf.template rename to root-common/usr/share/container-scripts/mysql/pre-init/my-slave.cnf.template diff --git a/s2i-common/bin/assemble b/s2i-common/bin/assemble new file mode 100755 index 00000000..d65b7e0c --- /dev/null +++ b/s2i-common/bin/assemble @@ -0,0 +1,13 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +shopt -s dotglob +echo "---> Installing application source ..." +mv /tmp/src/* ./ 2>/dev/null || true + +# Fix source directory permissions +/usr/libexec/fix-permissions ./ + diff --git a/s2i-common/bin/run b/s2i-common/bin/run new file mode 120000 index 00000000..4b21ab59 --- /dev/null +++ b/s2i-common/bin/run @@ -0,0 +1 @@ +/bin/run-mysqld \ No newline at end of file diff --git a/s2i-common/bin/usage b/s2i-common/bin/usage new file mode 100755 index 00000000..d6a3b9a7 --- /dev/null +++ b/s2i-common/bin/usage @@ -0,0 +1,8 @@ +#!/bin/sh + +set -o errexit +set -o nounset +set -o pipefail + +groff -t -man -ETascii /help.1 + diff --git a/test/run b/test/run index 66c2fea6..6e3ad6d8 100755 --- a/test/run +++ b/test/run @@ -10,10 +10,27 @@ set -o errexit set -o nounset shopt -s nullglob -IMAGE_NAME=${IMAGE_NAME-centos/mysql-${VERSION//.}-centos7-candidate} +TEST_LIST="\ +run_container_creation_tests +run_configuration_tests +run_general_tests +run_change_password_test +run_replication_test +run_doc_test +run_s2i_test +run_ssl_test +" + +if [ -e "${IMAGE_NAME:-}" ] ; then + echo "Error: IMAGE_NAME must be specified" + exit 1 +fi CIDFILE_DIR=$(mktemp --suffix=mysql_test_cidfiles -d) TESTSUITE_RESULT=1 +test_dir=$(readlink -zf $(dirname "${BASH_SOURCE[0]}")) + +s2i_args="--force-pull=false " function cleanup() { local cidfile @@ -58,7 +75,7 @@ function mysql_cmd() { local container_ip="$1"; shift local login="$1"; shift local password="$1"; shift - docker run --rm "$IMAGE_NAME" mysql --host "$container_ip" -u"$login" -p"$password" "$@" db + docker run --rm ${CONTAINER_EXTRA_ARGS:-} "$IMAGE_NAME" mysql --host "$container_ip" -u"$login" -p"$password" "$@" db } function test_connection() { @@ -164,6 +181,27 @@ function run_replication_test() { fi sleep 1 done + + # do some real work to test replication in practice + mysql_cmd "$master_ip" root root -e "CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (24);" + + # read value from slave and check whether it is expectd + for i in $(seq $max_attempts); do + set +e + result="$(mysql_cmd "${slave_ip}" root root -e "select * from t1 \G" | grep -e ^a | grep 24)" + set -e + if [[ ! -z "${result}" ]]; then + echo "${slave_ip} successfully got value from MASTER ${master_ip}" + break + fi + if [[ "${i}" == "${max_attempts}" ]]; then + echo "The ${slave_ip} failed to see value added on MASTER" + echo "Dumping logs for $(get_cid slave.cid)" + docker logs $(get_cid slave.cid) + return 1 + fi + sleep 1 + done } function assert_login_access() { @@ -403,28 +441,115 @@ run_doc_test() { echo } -# Tests. +_s2i_test_image() { + local container_name="$1" + local mount_opts="$2" + echo " Testing s2i app image with invalid configuration" + assert_container_creation_fails -e MYSQL_USER=root -e MYSQL_PASSWORD=pass -e MYSQL_DATABASE=db -e MYSQL_ROOT_PASSWORD=pass + echo " Testing s2i app image with correct configuration" + create_container \ + "$container_name" \ + --env MYSQL_USER=config_test_user \ + --env MYSQL_PASSWORD=config_test \ + --env MYSQL_DATABASE=db \ + --env MYSQL_OPERATIONS_USER=operations_user \ + --env MYSQL_OPERATIONS_PASSWORD=operations_pass \ + ${mount_opts} -run_container_creation_tests + test_connection "$container_name" operations_user operations_pass -run_configuration_tests + configuration="$(docker exec -t "$(get_cid $container_name)" bash -c 'set +f; shopt -s nullglob; egrep -hv "^(#|\!|\[|$)" /etc/my.cnf /etc/my.cnf.d/* /opt/rh/mysql*/root/etc/my.cnf /opt/rh/mysql*/root/etc/my.cnf.d/*' | sed 's,\(^[[:space:]]\+\|[[:space:]]\+$\),,' | sort -u)" -# Set lower buffer pool size to avoid running out of memory. -export CONTAINER_ARGS="run-mysqld --innodb_buffer_pool_size=5242880" + docker stop "$(get_cid $container_name)" >/dev/null +} -# Normal tests -USER=user PASS=pass run_tests no_root -USER=user1 PASS=pass1 ROOT_PASS=r00t run_tests root -# Test with arbitrary uid for the container -DOCKER_ARGS="-u 12345" USER=user PASS=pass run_tests no_root_altuid -DOCKER_ARGS="-u 12345" USER=user1 PASS=pass1 ROOT_PASS=r00t run_tests root_altuid +run_s2i_test() { + echo " Testing s2i usage" + s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null -# Test the password change -run_change_password_test + echo " Testing s2i build" + s2i build file://${test_dir}/test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp + local image_name_backup=${IMAGE_NAME} + export IMAGE_NAME=${IMAGE_NAME}-testapp -# Replication tests -run_replication_test + local container_name=s2i_config_build + _s2i_test_image "s2i_config_build" "" -run_doc_test + # return back original value for IMAGE_NAME + export IMAGE_NAME=${image_name_backup} + + echo " Testing s2i mount" + test_app_dir=$(mktemp -d) + cp -Lr ${test_dir}/test-app ${test_app_dir}/ + chown -R 27:27 ${test_app_dir} + _s2i_test_image "_s2i_test_mount" "-v ${test_app_dir}/test-app:/opt/app-root/src/:z" + rm -rf ${test_app_dir} + echo " Success!" +} + +gen_self_signed_cert() { + local output_dir=$1 ; shift + local base_name=$1 ; shift + mkdir -p ${output_dir} + openssl req -newkey rsa:2048 -nodes -keyout ${output_dir}/${base_name}-key.pem -subj '/C=GB/ST=Berkshire/L=Newbury/O=My Server Company' > ${base_name}-req.pem + openssl req -new -x509 -nodes -key ${output_dir}/${base_name}-key.pem -batch > ${output_dir}/${base_name}-cert-selfsigned.pem +} + +run_ssl_test() { + echo " Testing ssl usage" + test_app_dir=$(mktemp -d) + mkdir -p ${test_app_dir}/{mysql-certs,mysql-cfg} + gen_self_signed_cert ${test_app_dir}/mysql-certs server + echo "[mysqld] +ssl-key=\${APP_DATA}/mysql-certs/server-key.pem +ssl-cert=\${APP_DATA}/mysql-certs/server-cert-selfsigned.pem +" >${test_app_dir}/mysql-cfg/ssl.cnf + chown -R 27:27 ${test_app_dir} + local ca_cert_path="/opt/app-root/src/mysql-certs/server-cert-selfsigned.pem" + + create_container \ + "_s2i_test_ssl" \ + --env MYSQL_USER=ssl_test_user \ + --env MYSQL_PASSWORD=ssl_test \ + --env MYSQL_DATABASE=db \ + -v ${test_app_dir}:/opt/app-root/src/:z + + test_connection "_s2i_test_ssl" ssl_test_user ssl_test + ip=$(get_container_ip _s2i_test_ssl) + + # At least MySQL 5.6 requires ssl-ca option on client side, otherwise the ssl is not used + CONTAINER_EXTRA_ARGS="-v ${test_app_dir}:/opt/app-root/src/:z" + if mysql_cmd "$ip" "ssl_test_user" "ssl_test" --ssl-mode=REQUIRED --ssl-ca=${ca_cert_path} -e 'show status like "Ssl_cipher" \G' | grep 'Value: [A-Z][A-Z0-9-]*' ; then + echo " Success!" + rm -rf ${test_app_dir} + else + echo " FAIL!" + mysql_cmd "$ip" "ssl_test_user" "ssl_test" --ssl-ca=${ca_cert_path} -e 'show status like "%ssl%" \G' + return 1 + fi +} + +function run_general_tests() { + # Set lower buffer pool size to avoid running out of memory. + export CONTAINER_ARGS="run-mysqld --innodb_buffer_pool_size=5242880" + + # Normal tests + USER=user PASS=pass run_tests no_root + USER=user1 PASS=pass1 ROOT_PASS=r00t run_tests root + # Test with arbitrary uid for the container + DOCKER_ARGS="-u 12345" USER=user PASS=pass run_tests no_root_altuid + DOCKER_ARGS="-u 12345" USER=user1 PASS=pass1 ROOT_PASS=r00t run_tests root_altuid +} + +function run_all_tests() { + for test_case in $TEST_LIST; do + : "Running test $test_case" + $test_case + done; +} + +# Run the chosen tests +TEST_LIST=${@:-$TEST_LIST} run_all_tests TESTSUITE_RESULT=0 + diff --git a/test/test-app b/test/test-app new file mode 120000 index 00000000..1c25ae34 --- /dev/null +++ b/test/test-app @@ -0,0 +1 @@ +../examples/extend-image/ \ No newline at end of file