Home Blog CV Projects Patterns Notes Book Colophon Search

Notes on App Engine Custom Runtimes with Docker in the Google Cloud

1 Dec, 2016

Before this guide makes much sense, you'll need to follow the Get Started with Docker guide.

1. Install Docker and run hello-world

This introduces the docker run command, and also docker ps and docker ps -a.

I also ran the example that it shows in the output when you complete the above step:

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

The -it flag allows you to stay with the running program in the container.

This gives you a bash prompt in a running Ubuntu container. You can run commands like this one:

root@f76401fcc701:/# du -hs /
du: cannot access '/proc/10/task/10/fd/3': No such file or directory
du: cannot access '/proc/10/task/10/fdinfo/3': No such file or directory
du: cannot access '/proc/10/fd/4': No such file or directory
du: cannot access '/proc/10/fdinfo/4': No such file or directory
123M	/
root@f76401fcc701:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
none             60G  394M   56G   1% /
tmpfs           999M     0  999M   0% /dev
tmpfs           999M     0  999M   0% /sys/fs/cgroup
/dev/vda2        60G  394M   56G   1% /etc/hosts
shm              64M     0   64M   0% /dev/shm
root@f76401fcc701:/# free -m
              total        used        free      shared  buff/cache   available
Mem:           1997          60        1346         156         590        1620
Swap:          3994           0        3994

2. Learn about images and containers

3. Find and run the whalesay image

This demonstrates how to find existing software on docker hub and introduces the docker images command.

It also demonstrates how you can pass arguments to commands run in the conatiner:

$ docker run docker/whalesay cowsay boo-boo
    _________
< boo-boo >
 ---------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/ 

Google Custom Runtime Nginx Sample

After this you can follow the Google Custom Runtime quickstart but even if you don't want to deploy the example, you can run it locally.

git clone https://github.com/GoogleCloudPlatform/appengine-custom-runtimes-samples.git
cd appengine-custom-runtimes-samples/nginx

You can now build the container by giving it a name and specifying the current directory with a .:

docker build -t local-nginx .

Then you can run the image:

docker run local-nginx

And look at running containers:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
b07e46ccc1af        local-nginx         "nginx -g 'daemon off"   21 seconds ago      Up 20 seconds       80/tcp, 443/tcp     high_goldwasser

You can see that the image uses ports 80 and 443. This is becuase our Dockerfile specifies FROM nginx, and the nginx Dockerfile specifies EXPOSE 80 443

You can see from the nginx.conf that the server is actually running on 8080 but this is not exposed. We need to map a port on the local machine to a port in the container.

Stop the container with Ctrl+C and then run it again with the mapping:

docker run -p 8080:8080 local-nginx

This time the port mapping looks like this:

$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                     NAMES
4dae50e0d425        local-nginx         "nginx -g 'daemon off"   9 seconds ago       Up 8 seconds        80/tcp, 443/tcp, 0.0.0.0:8080->8080/tcp   cocky_ride

You can now fetch the page served based on www/index.html from the running Nginx server:

$ curl http://localhost:8080
<!doctype html>
<!--
  Copyright 2015 Google Inc.
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Welcome to nginx!</h1>
    <p>Brought to you by Google App Engine.</p>
  </body>
</html>

Volume Mounting

To get a bit more fancy, you can mount local files into the running docker container so you can change them locally like this:

$ git diff
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
index d2a1ba7..e165994 100644
--- a/nginx/Dockerfile
+++ b/nginx/Dockerfile
@@ -20,5 +20,5 @@ RUN mkdir -p /usr/share/nginx/www/_ah && \
     echo "healthy" > /usr/share/nginx/www/_ah/health
 
 # Finally, all static assets.
-ADD www/ /usr/share/nginx/www/
-RUN chmod -R a+r /usr/share/nginx/www
+# ADD www/ /usr/share/nginx/www/
+# RUN chmod -R a+r /usr/share/nginx/www

You'll need to rebuild the image. If you give it the same name the old one will be overwritten:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
local-nginx         latest              8c1e7d8edfbb        40 minutes ago      181.5 MB
$ docker build -t local-nginx .
Sending build context to Docker daemon 9.728 kB
Step 1 : FROM nginx
 ---> abf312888d13
Step 2 : COPY nginx.conf /etc/nginx/nginx.conf
 ---> Using cache
 ---> 809f97c0d8b4
Step 3 : RUN mkdir -p /var/log/app_engine
 ---> Using cache
 ---> 00253b89a16e
Step 4 : RUN mkdir -p /usr/share/nginx/www/_ah &&     echo "healthy" > /usr/share/nginx/www/_ah/health
 ---> Using cache
 ---> 7fd0613b89da
Successfully built 7fd0613b89da
nd29030:nginx jgardner$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
local-nginx         latest              7fd0613b89da        40 minutes ago      181.5 MB

Notice how the IMAGE ID has changed.

Now you can map a volume too:

$ docker run -p 8080:8080 --volume $PWD/www:/usr/share/nginx/www local-nginx

Tip:

If you put the --volume flag at the end by mistake you'll get an error like this:

$ docker run -p 8080:8080 local-nginx --volume $PWD/www:/usr/share/nginx/www
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"--volume\\\": executable file not found in $PATH\"\n".

If you try to use a relative path such as --volume www:/usr/share/nginx/www Docker won't complain, but the volume won't be mounted.

Connecting to a running instance

To connect to a running instance you can use this trick...

First, work out the container name for the image you are using:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                     NAMES
d49ab1018b7f        local-nginx         "nginx -g 'daemon off"   3 minutes ago       Up 3 minutes        80/tcp, 443/tcp, 0.0.0.0:8080->8080/tcp   goofy_cori

in this case goofy_cori, and then run this:

$ docker exec -it goofy_cori /bin/bash

From within the container, you can now explore:

root@d49ab1018b7f:/#  ls -l /usr/share/nginx/   
total 4
drwxr-xr-x 2 root root 4096 Nov 28 18:12 html
drwxr-xr-x 3 root root  102 Nov 30 12:37 www

Now that the directories are in sync, you can make changes to your local www directory and they'll be reflected in the container. When you remove images, you need to also tell docker to stop keeping track of the volume, otherwise you'll eventually run out of memory.

Build a Docker setup

Google provide Docker images for their custom runtimes so that you can run your code locally in exactly the same environment as remotely.

https://cloud.google.com/appengine/docs/flexible/custom-runtimes/build

https://github.com/GoogleCloudPlatform/nodejs-docker

If you have the Google Cloud SDK and Beta Commands installed, you can create a custom Docker set up like this:

gcloud beta app gen-config --custom

Put your app.js in place and then run it with:

docker run -p 8080:8080 api

You can visit http://localhost:8080 and see the API running node in the container.

This doesn't handle Ctrl+C very well, so you can stop the container like this:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4d63f48ea9b5        api                 "/bin/sh -c 'npm star"   6 minutes ago       Up 6 minutes                            stupefied_shirley
$ docker stop stupefied_shirley
stupefied_shirley

Deploy

Once you are happy with the image you can deploy it to Google Cloud like this:

gcloud app deploy

To see your app run in the cloud, enter the following address in your web browser:

https://.appspot.com

Copyright James Gardner 1996-2020 All Rights Reserved. Admin.