{"id":593,"date":"2018-11-27T12:16:55","date_gmt":"2018-11-27T20:16:55","guid":{"rendered":"http:\/\/andrewwippler.com\/?p=593"},"modified":"2018-11-27T12:16:55","modified_gmt":"2018-11-27T20:16:55","slug":"docker-izing-wordpress-for-kubernetes","status":"publish","type":"post","link":"https:\/\/andrewwippler.com\/2018\/11\/27\/docker-izing-wordpress-for-kubernetes\/","title":{"rendered":"Docker-izing WordPress for Kubernetes"},"content":{"rendered":"\n\nWordPress is amazingly popular considering how antiquated the file structure and code appears to be. Even still, it is the easiest CMS that I have used and the community has created plugins to make the copy-folder-for-a-new-theme\/plugin at least tolerable. A challenge comes when one wants to use the 1990’s method of serving web applications in a more modern way (such as running inside a container on top of Kubernetes).\n\n\n\n\n\nContainers are meant to be immutable and treated as read-only. (No change to files in the container after they are built.) Containers are supposed to be a point-in-time release of software. As such, I can roll-back to a specific container version and have that specific code running. This causes a problem when one wants to use a file-dependent application such as WordPress. The best I could come up with for running WordPress in a container is a forward-only method of deploying code (basically, giving up the ability to use a previous version of code.) There\u00a0is<\/strong>\u00a0a way to keep that functionality, but it would mean storing everything (including uploads) inside an ever-growing container or using a central object store such as S3 for uploads. It would also require a re-build of the container every time a plugin is updated – which would presumably be every hour.\n\n\n\n\n\nMy deployments of WordPress are so little that I can hardly justify using S3 for uploads, keeping the plugins in sync, and going backwards in time.\n\n\n\n\n\nWhen deploying to Kubernetes, one can scale the replicas to N copies. Keeping plugins, themes, and updates the same across all replicas will require a READ WRITE MANY (rwx) volume to be shared. This could be a GlusterFS volume or NFS, but it cannot be a AWS EBS volume or any other single-use block storage.\n\n\n\n\n\nWhen looking at the available WordPress images, there are three that seem interesting. With the official image, I like that I can use php-fpm and alpine. The next top two implementations of WordPress have very<\/a> bloated<\/a> docker files. I have come to the conclusion that my WordPress container will have to be built from scratch.\n\n\n\n\n\n
\n\nThe Dockerfile<\/a> is very similar to the official WordPress container. It uses php:7.2-fpm-alpine<\/code> as the base image, adds in nginx, and inserts a generic wp-config.php<\/a> file.\n\n<\/div>\n\n\n\n\n\n
\n\nThe folder structure for the container is as follows:\n
WordPress Container Folder\n\u251c\u2500\u2500 docker-entrypoint.sh\n\u251c\u2500\u2500 Dockerfile\n\u251c\u2500\u2500 html\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 ... Contents of wordpress-X.Y.Z.zip\n\u251c\u2500\u2500 nginx.conf\n\u2514\u2500\u2500 wp-config.php\n<\/code><\/pre>\nIt can be built by running a command similar to docker build -t andrewwippler\/wordpress:latest .<\/code>\n\n<\/div>\n\n\n\n\n\nnginx.conf<\/a> is a very basic configuration file with gzip and cache headers. The real neat things come in the docker-entrypoint.sh<\/a> file.\n\n\n\n\n\n
\n\nI borrowed<\/a> the database creation script; however, since PHP was already installed in the container, I ran a few more checks in PHP rather than bash.\n\nFor instance, the container places the local code in \/var\/www\/html-original<\/code> and rsyncs it to \/var\/www\/html<\/code> where the webserver sees it, but it only does this if the code in html-original<\/code> is newer than html<\/code>. This allows an operator to mount a storage volume at \/var\/www\/html<\/code> which can be shared across Kubernetes Deployment replicas. The code for this is:\n
\/\/ see if we need to copy files over\ninclude '\/var\/www\/html-original\/wp-includes\/version.php';\n$dockerWPversion = $wp_version;\n\nif (file_exists('\/var\/www\/html\/wp-includes\/version.php')) {\n    include '\/var\/www\/html\/wp-includes\/version.php';\n    $installedWPversion = $wp_version;\n} else {\n    $installedWPversion = '0.0.0';\n}\n\nfwrite($stderr, \"dockerWPversion: $dockerWPversion - installedWPversion: $installedWPversion\\n\");\nif(version_compare($dockerWPversion, $installedWPversion, '>')) {\n    fwrite($stderr, \"Installing wordpress files\\n\");\n    exec('rsync -au \/var\/www\/html-original\/ \/var\/www\/html');\n}\n<\/code><\/pre>\nI have also included a theme-only check that will update the theme if it has changed. This is necessary to update the theme files when the version of WordPress has not changed.\n
if (filemtime('\/var\/www\/html-original\/wp-content\/themes') > filemtime('\/var\/www\/html\/wp-content\/themes')) {\n    fwrite($stderr, \"Updating theme files\\n\");\n    exec('rsync -au --delete-after \/var\/www\/html-original\/wp-content\/themes\/ \/var\/www\/html\/wp-content\/themes');\n}\n<\/code><\/pre>\n<\/div>\n\n\n\n\n\nAll files I have referenced in this article are located in a gist<\/a>. In addition to those files a local docker-compose.yml file might be helpful for your local development:\n\n\n\n\n\n
\n
version: '2'\nservices:\n  db:\n    image: mariadb:10\n    volumes:\n      - .\/tmp\/db:\/var\/lib\/mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=secretPASS\n\n  wordpress:\n    build: wordpress\n    volumes:\n      - .\/html:\/var\/www\/html\n      - .\/nginx.conf:\/etc\/nginx\/nginx.conf:ro\n    links:\n      - db\n    environment:\n      - WORDPRESS_DB_NAME=wordpress\n      - WORDPRESS_DB_HOST=db\n      - WORDPRESS_DB_USER=root\n      - WORDPRESS_DB_PASSWORD=secretPASS\n    ports:\n      - 8080:80\n<\/code><\/pre>\n<\/div>","protected":false},"excerpt":{"rendered":"

WordPress is amazingly popular considering how antiquated the file structure and code appears to be. Even still, it is the easiest CMS that I have used and the community has created plugins to make the copy-folder-for-a-new-theme\/plugin at least tolerable. A challenge comes when one wants to use the 1990’s method of serving web applications in […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[11],"tags":[16,75,18,51,83,46,43],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/593"}],"collection":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/comments?post=593"}],"version-history":[{"count":2,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/593\/revisions"}],"predecessor-version":[{"id":595,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/593\/revisions\/595"}],"wp:attachment":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/media?parent=593"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/categories?post=593"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/tags?post=593"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}