{"id":544,"date":"2018-08-14T10:40:46","date_gmt":"2018-08-14T10:40:46","guid":{"rendered":"https:\/\/www.peopleperhour.com\/engineering\/?p=544"},"modified":"2018-08-28T13:26:02","modified_gmt":"2018-08-28T13:26:02","slug":"working-around-the-lack-of-volumes-from-in-kubernetes","status":"publish","type":"post","link":"https:\/\/www.peopleperhour.com\/engineering\/2018\/08\/14\/working-around-the-lack-of-volumes-from-in-kubernetes\/","title":{"rendered":"Working around the lack of volumes-from in Kubernetes"},"content":{"rendered":"<p>We have been running containerised apps since early 2014 so in theory, moving to Kubernetes should be easy. The problem is that we currently make use of the <code>--volumes-from<\/code> docker flag to share volumes between containers of the same app. For example, a common pattern we use is to put our webapp sourcecode in a data-container and then run both nginx and a app-server like php-fpm or nodejs with the <code>--volumes-from<\/code> flag, referencing the data-container, thereby ensuring all processes have a common view of the sourcecode. This is very useful for things like dynamically generated asset files such as CSS. For example, if the PHP container creates a CSS file, the nginx container can serve it as they share the same volume. Kubernetes doesn&#8217;t have <code>--volumes-from<\/code>. If you want a single Pod to have both nginx and php-fpm containers, how do you share data between them in a way where the data is visible at container launch AND can be modified by any container in a manner that the others get the changes immediately?<\/p>\n<p>One approach is to copy the data you want to share (e.g. sourcecode) to a volume in a <code>initContainers<\/code> so that when the containers start they can mount the volume in order to share it. We use <code>emptyDir<\/code> and this is what it looks like:<\/p>\n<pre class=\"yaml\">---\r\napiVersion: apps\/v1\r\nkind: Deployment\r\nspec:\r\n  replicas: 2\r\n\r\n  template:\r\n\r\n    spec:\r\n\r\n      initContainers:\r\n\r\n      # Copy the webapp sourcecode into a volume that both nginx and fpm will mount\r\n      - name: init-sourcecode\r\n        image: peopleperhour\/data:{{ version }}\r\n        command: [\"bin\/sh\", \"-c\", \"cp -r \/var\/www\/ourapp\/. \/code && echo 'some other stuff, e.g. set permissions'\"]\r\n        volumeMounts:\r\n        - mountPath: \/code\r\n          name: app-volume\r\n\r\n      containers:\r\n\r\n      - name: fpm\r\n        image: peopleperhour\/fpm\r\n        volumeMounts:\r\n        - name: app-volume\r\n          mountPath: \/var\/www\/ourapp\r\n        - name: php-socket\r\n          mountPath: \/sock\r\n\r\n      - name: nginx\r\n        image: peopleperhour\/nginx\r\n        ports:\r\n        - containerPort: 80\r\n        volumeMounts:\r\n        - name: app-volume\r\n          mountPath: \/var\/www\/ourapp\r\n        - name: php-socket\r\n          mountPath: \/sock\r\n\r\n      volumes:\r\n\r\n      # For our webapp sourcecode, e.g. the executable PHP.\r\n      - name: app-volume\r\n        emptyDir: {}\r\n      # For a unix socket so nginx and fpm can communicate\r\n      - name: php-socket\r\n        emptyDir: {}\r\n<\/pre>\n<p><\/p>\n<p>This mimics the old <code>--volumes-from<\/code> quite well but it makes the startup time of the pod a little slow because if your app is 300M then you&#8217;re copying 300M to disk in the init phase for each Pod. This is not a problem for a few Pods but if you run dozens of Pods and if you run CronJobs that run every minute the disk I\/O is noticeable. We found we were running out of AWS EBS disk I\/O credits due to all the moving of bits about.<\/p>\n<p>One alternative idea we came up with is to use &#8220;Docker-outside-of-Docker&#8221; (DooD) to bake together the app with the data as a local docker image. This way, the app can start very quickly <em>on subsequent startups<\/em> without needing to copy to disk. DooD is the name given to the technique of mounting the docker socket so you can run docker commands and get the same results as if you ran them directly on the host. In our case, it allow us to run <code>docker build<\/code> from within a container running in Kubernetes. This is what it looks like in a Kubernetes <code>CronJob<\/code>:<\/p>\n<pre class=\"yaml\">---\r\napiVersion: batch\/v1beta1\r\nkind: CronJob\r\nspec:\r\n  schedule: \"* * * * *\"\r\n  startingDeadlineSeconds: 59\r\n  jobTemplate:\r\n    spec:\r\n      template:\r\n        spec:\r\n          initContainers:\r\n          # Create a local docker image that has php and the sourcecode in it\r\n          # Note: Only do the build IF not already done.\r\n          - name: build-php\r\n            image: docker:18.05\r\n            imagePullPolicy: IfNotPresent\r\n            command: [\"bin\/sh\", \"-c\", \"[ $(docker images -q mylocalimage:{{ version }})\\\" != '' ] || (echo -e \\\"FROM peopleperhour\/data:{{ version }}\\\\nFROM peopleperhour\/fpm\\\\nCOPY --from=0 \/var\/www\/ourapp .\\\" | docker build -t mylocalimage:{{ version }} -)\"]\r\n            volumeMounts:\r\n            - name: docker-sock\r\n              mountPath: \/var\/run\r\n\r\n          containers:\r\n          - name: cron\r\n            image: mylocalimage:{{ version }}\r\n            imagePullPolicy: Never\r\n            command: [\"start-crons.sh\"]\r\n\r\n          volumes:\r\n\r\n          # For doing docker builds within a docker container (Docker-outside-of-Docker)\r\n          - name: docker-sock\r\n            hostPath:\r\n                path: \/var\/run\r\n<\/pre>\n<p><\/p>\n<p>The <code>initContainer<\/code> runs a docker build using a self-created Dockerfile. This creates a local docker image that can be used next time the cron runs. It is not best practice to mount the docker socket into a Pod &#8211; it is akin to running in &#8220;privileged mode&#8221;, so it may not be the best approach for you.<\/p>\n<p>Notes:<\/p>\n<ul>\n<li>In our use case, we didn&#8217;t want to push the image to a docker registry, we wanted to keep in only on the local node to avoid network delays. This is why the container uses <code>imagePullPolicy: Never<\/code>.<\/li>\n<li>The builder container can use a official docker image which has the docker binary in it, e.g. <code>docker:18.05<\/code><\/li>\n<li>We pass a string straight to <code>docker build<\/code> avoiding the need of a <code>Dockerfile<\/code><\/li>\n<li>If you need to pull from a private docker registry as part of your docker build, the normal Kubernetes <code>imagePullSecrets<\/code> doesn&#8217;t work. We found a workaround where we could mount a docker authentication secret into own <code>initContainer<\/code>. This is what our secret volume looked like, the trick being to use a <code>key<\/code> and <code>path<\/code>:\n<pre class=\"yaml\">---\r\n          - name: container-registry-login\r\n            secret:\r\n              secretName: my-login-name  # This should be the name usually used in your imagePullSecrets directive.\r\n              items:\r\n               - key: .dockerconfigjson\r\n                 path: config.json       # This allows the docker login secret to be mounted as \/root\/.docker\/config.json\r\n<\/pre>\n<p>\n<\/li>\n<\/ul>\n<p>I&#8217;m interested to know if folk out there have any other methods to workaround not having volumes-from in Kubernetes?<\/p>\n<hr>\n<p>Resource(s):<\/p>\n<ul>\n<li>A case for Docker-in-Docker on Kubernetes : <a href=\"https:\/\/applatix.com\/case-docker-docker-kubernetes-part-2\/\">https:\/\/applatix.com\/case-docker-docker-kubernetes-part-2\/<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>We have been running containerised apps since early 2014 so in theory, moving to Kubernetes should be easy. The problem is that we currently make use of the &#8211;volumes-from docker flag to share volumes between containers of the same app. For example, a common pattern&#8230;<\/p>\n","protected":false},"author":40,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[23],"tags":[14,44],"class_list":["post-544","post","type-post","status-publish","format-standard","hentry","category-devops-2","tag-devops","tag-kubernetes"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p2CA4w-8M","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/posts\/544","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/users\/40"}],"replies":[{"embeddable":true,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/comments?post=544"}],"version-history":[{"count":13,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/posts\/544\/revisions"}],"predecessor-version":[{"id":558,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/posts\/544\/revisions\/558"}],"wp:attachment":[{"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/media?parent=544"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/categories?post=544"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.peopleperhour.com\/engineering\/wp-json\/wp\/v2\/tags?post=544"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}