Everybody is talking about Docker nowadays. What it is about? Do you remember Solaris Zones or Containers? It is more or less the same although development of Docker during the last years made Linux Containers the de-facto standard for deploying applications in a standardized and isolated way. Docker is build in a classical client server model: There is the docker server (or daemon) which servers the requests of docker clients. The client is the one you’ll use to tell the server what you want to do. The main difference from the classical client/server model is that docker uses the same binary for the server as well as for the client. It is just a matter of how you invoke the docker binary that makes it a server or client application. In contrast to the Solaris Zones Docker containers are stateless by default, that means: When you shutdown a docker container you’ll lose everything that was done when the container started to what happened when container got destroyed (Although there are ways to avoid that). This is important to remember.
When you start a docker container on a host the host’s resources are shared with the container (Although you can limit that). It is not like when you fire up a virtual machine (which brings up an instance of a whole operating system) but more like a process that shares resources with the host it is running on. This might be as simple as running a “wget” command but it might be as complicated as bringing up a whole infrastructure that serves your service desk. Docker containers should be lightweight.
So what does make docker unique then? It is the concept of a layered filesystem. We’ll come to that soon. Lets start by installing everything we need to run a docker daemon. As always we’ll start with as CentOS 7 minimal installation:
1 2 3 | [root@centos7 ~]$ cat /etc/centos-release CentOS Linux release 7.2.1511 (Core) [root@centos7 ~]$ |
The easiest way to get docker installed is to add the official docker yum repository (for CentOS in this case):
1 2 3 4 5 6 | [root@centos7 ~]$ echo "[dockerrepo] name=Docker Repository baseurl=https: //yum .dockerproject.org /repo/main/centos/7/ enabled=1 gpgcheck=1 gpgkey=https: //yum .dockerproject.org /gpg " > /etc/yum .repos.d /docker .repo |
Working directly as root never is a good idea so lets create a user for that and let this user do everything via sudo ( not a good practice, I know ):
1 2 3 4 5 6 | [root@centos7 ~]$ groupadd docker [root@centos7 ~]$ useradd -g docker docker [root@centos7 ~]$ passwd docker [root@centos7 ~]$ echo "docker ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers [root@centos7 ~]$ su - docker [docker@centos7 ~]$ sudo ls |
Ready to install:
1 | [docker@centos7 ~]$ sudo yum install docker-engine |
This will install the docker engine and these additional packages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ====================================================================================================================================== Package Arch Version Repository Size ====================================================================================================================================== Installing: docker-engine x86_64 1.12.3-1.el7.centos dockerrepo 19 M Installing for dependencies: audit-libs-python x86_64 2.4.1-5.el7 base 69 k checkpolicy x86_64 2.1.12-6.el7 base 247 k docker-engine-selinux noarch 1.12.3-1.el7.centos dockerrepo 28 k libcgroup x86_64 0.41-8.el7 base 64 k libseccomp x86_64 2.2.1-1.el7 base 49 k libsemanage-python x86_64 2.1.10-18.el7 base 94 k libtool-ltdl x86_64 2.4.2-21.el7_2 updates 49 k policycoreutils-python x86_64 2.2.5-20.el7 base 435 k python-IPy noarch 0.75-6.el7 base 32 k setools-libs x86_64 3.3.7-46.el7 base 485 k Transaction Summary ====================================================================================================================================== |
Enable the service:
1 2 | [docker@centos7 ~]$ sudo systemctl enable docker.service Created symlink from /etc/systemd/system/multi-user .target.wants /docker .service to /usr/lib/systemd/system/docker .service. |
Start the service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [docker@centos7 ~]$ sudo systemctl start docker [docker@centos7 ~]$ sudo systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded ( /usr/lib/systemd/system/docker .service; enabled; vendor preset: disabled) Active: active (running) since Sat 2016-12-10 12:26:46 CET; 6s ago Docs: https: //docs .docker.com Main PID: 2957 (dockerd) Memory: 12.9M CGroup: /system .slice /docker .service ├─2957 /usr/bin/dockerd └─2960 docker-containerd -l unix: ///var/run/docker/libcontainerd/docker-containerd .sock --shim docker-containerd-shim --... Dec 10 12:26:45 centos7. local dockerd[2957]: time = "2016-12-10T12:26:45.481380483+01:00" level=info msg= "Graph migration to co...conds" Dec 10 12:26:45 centos7. local dockerd[2957]: time = "2016-12-10T12:26:45.481751429+01:00" level=warning msg= "mountpoint for pid...found" Dec 10 12:26:45 centos7. local dockerd[2957]: time = "2016-12-10T12:26:45.481751451+01:00" level=info msg= "Loading containers: start." Dec 10 12:26:45 centos7. local dockerd[2957]: time = "2016-12-10T12:26:45.574330143+01:00" level=info msg= "Firewalld running: false" Dec 10 12:26:45 centos7. local dockerd[2957]: time = "2016-12-10T12:26:45.822997195+01:00" level=info msg= "Default bridge (docke...dress" Dec 10 12:26:46 centos7. local dockerd[2957]: time = "2016-12-10T12:26:46.201798804+01:00" level=info msg= "Loading containers: done." Dec 10 12:26:46 centos7. local dockerd[2957]: time = "2016-12-10T12:26:46.201984648+01:00" level=info msg= "Daemon has completed ...ation" Dec 10 12:26:46 centos7. local dockerd[2957]: time = "2016-12-10T12:26:46.202003760+01:00" level=info msg= "Docker daemon" commit...1.12.3 Dec 10 12:26:46 centos7. local dockerd[2957]: time = "2016-12-10T12:26:46.207416263+01:00" level=info msg= "API listen on /var/ru....sock" Dec 10 12:26:46 centos7. local systemd[1]: Started Docker Application Container Engine. Hint: Some lines were ellipsized, use -l to show in full. |
And we’re done. Lets check if docker is working as expected:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | [docker@centos7 ~]$ sudo docker run -- rm hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library /hello-world c04b14da8d14: Pull complete Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker Hub account: https: //hub .docker.com For more examples and ideas, visit: https: //docs .docker.com /engine/userguide/ |
What happened here is that we already executed our first docker image: “hello-world”. The “–rm” flag tells docker to automatically remove the image once it exits. As the image was not available on our host it was automatically downloaded from the docker hub:
1 2 3 4 5 | Unable to find image 'hello-world:latest' locally latest: Pulling from library /hello-world c04b14da8d14: Pull complete Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9 Status: Downloaded newer image for hello-world:latest |
You can browse the docker hub for many, many other images using your favorite browser or you can use the command line:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | [docker@centos7 ~]$ docker search postgres NAME DESCRIPTION STARS OFFICIAL AUTOMATED postgres The PostgreSQL object-relational database ... 2939 [OK] kiasaki /alpine-postgres PostgreSQL docker image based on Alpine Linux 28 [OK] abevoelker /postgres Postgres 9.3 + WAL-E + PL /V8 and PL /Python ... 10 [OK] onjin /alpine-postgres PostgreSQL / v9.1 - v9.6 / size < 50MB 9 [OK] macadmins /postgres Postgres that accepts remote connections b... 8 [OK] jamesbrink /postgres Highly configurable PostgreSQL container. 5 [OK] eeacms /postgres Docker image for PostgreSQL (RelStorage re... 4 [OK] cptactionhank /postgres 4 [OK] azukiapp /postgres Docker image to run PostgreSQL by Azuki - ... 2 [OK] kampka /postgres A postgresql image build on top of an arch... 2 [OK] clkao /postgres-plv8 Docker image for running PLV8 1.4 on Postg... 2 [OK] 2020ip /postgres Docker image for PostgreSQL with PLV8 1 [OK] steenzout /postgres Steenzout's docker image packaging for Pos.1 [OK] blacklabelops /postgres Postgres Image for Atlassian Applications 1 [OK] buker /postgres postgres 0 [OK] kobotoolbox /postgres Postgres image for KoBo Toolbox. 0 [OK] vrtsystems /postgres PostgreSQL image with added init hooks, bu... 0 [OK] timbira /postgres Postgres containers 0 [OK] coreroller /postgres official postgres:9.4 image but it adds 2 ... 0 [OK] livingdocs /postgres Postgres v9.3 with the plv8 extension inst... 0 [OK] 1maa /postgres PostgreSQL base image 0 [OK] opencog /postgres This is a configured postgres database for ... 0 [OK] khipu /postgres postgres with custom uids 0 [OK] travix /postgres A container to run the PostgreSQL database. 0 [OK] beorc /postgres Ubuntu-based PostgreSQL server 0 [OK] |
The first one is the official PostgreSQL image. How do I run it?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | [docker@centos7 ~]$ docker run -it postgres Unable to find image 'postgres:latest' locally latest: Pulling from library /postgres 386a066cd84a: Pull complete e6dd80b38d38: Pull complete 9cd706823821: Pull complete 40c17ac202a9: Pull complete 7380b383ba3d: Pull complete 538e418b46ce: Pull complete c3b9d41b7758: Pull complete dd4f9522dd30: Pull complete 920e548f9635: Pull complete 628af7ef2ee5: Pull complete 004275e6f5b5: Pull complete Digest: sha256:e761829c4b5ec27a0798a867e5929049f4cbf243a364c81cad07e4b7ac2df3f1 Status: Downloaded newer image for postgres:latest The files belonging to this database system will be owned by user "postgres" . This user must also own the server process. The database cluster will be initialized with locale "en_US.utf8" . The default database encoding has accordingly been set to "UTF8" . The default text search configuration will be set to "english" . Data page checksums are disabled. fixing permissions on existing directory /var/lib/postgresql/data ... ok creating subdirectories ... ok selecting default max_connections ... 100 selecting default shared_buffers ... 128MB selecting dynamic shared memory implementation ... posix creating configuration files ... ok running bootstrap script ... ok performing post-bootstrap initialization ... ok syncing data to disk ... ok WARNING: enabling "trust" authentication for local connections You can change this by editing pg_hba.conf or using the option -A, or --auth- local and --auth-host, the next time you run initdb. Success. You can now start the database server using: pg_ctl -D /var/lib/postgresql/data -l logfile start **************************************************** WARNING: No password has been set for the database. This will allow anyone with access to the Postgres port to access your database. In Docker's default configuration, this is effectively any other container on the same system. Use "-e POSTGRES_PASSWORD=password" to set it in "docker run" . **************************************************** waiting for server to start....LOG: database system was shut down at 2016-12-10 11:42:01 UTC LOG: MultiXact member wraparound protections are now enabled LOG: database system is ready to accept connections LOG: autovacuum launcher started done server started ALTER ROLE /docker-entrypoint .sh: ignoring /docker-entrypoint-initdb .d/* waiting for server to shut down....LOG: received fast shutdown request LOG: aborting any active transactions LOG: autovacuum launcher shutting down LOG: shutting down LOG: database system is shut down done server stopped PostgreSQL init process complete; ready for start up. LOG: database system was shut down at 2016-12-10 11:42:04 UTC LOG: MultiXact member wraparound protections are now enabled LOG: database system is ready to accept connections LOG: autovacuum launcher started |
And ready. As with the “hello:world” image docker had to download the image as it was not available locally. Once that was done the image was started and new PostgreSQL instance was created automatically. Here you can see what the layered filesystem is about:
1 2 3 4 5 6 7 8 9 10 11 | 386a066cd84a: Pull complete e6dd80b38d38: Pull complete 9cd706823821: Pull complete 40c17ac202a9: Pull complete 7380b383ba3d: Pull complete 538e418b46ce: Pull complete c3b9d41b7758: Pull complete dd4f9522dd30: Pull complete 920e548f9635: Pull complete 628af7ef2ee5: Pull complete 004275e6f5b5: Pull complete |
Each of this lines represents a layered/stacked filesystem on top of the previous one. This is an important concept because when you change things only the layer that contains the change needs to be rebuild, but not the layers below. In other words you could build an image based on a CentOS 7 image and then deploy your changes on top of that. You deliver that image and some time later you need to make some modifications: The only thing you need to deliver are the modifications you did because the layers below did not change.
You will notice that you cannot type any command when the image was started. As soon as you enter “CRTL-C” the container will shutdown (this is because of the “-it” switch, which is “interactive” and “pseudo terminal”):
1 2 3 4 5 | ^CLOG: received fast shutdown request LOG: aborting any active transactions LOG: autovacuum launcher shutting down LOG: shutting down LOG: database system is shut down |
Everything what happened inside the container is now gone. The correct way to launch it is:
1 2 | [docker@centos7 ~]$ docker run --name my-first-postgres -e POSTGRES_PASSWORD=postgres -d postgres d51abc52108d3040817474fa8c85ab15020c12cb753515543c2d064143277155 |
The “-d” switch tells docker to detach, so we get back our shell. The magic string dockers returns is the container id:
1 2 3 | [docker@centos7 ~]$ docker ps --no-trunc CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d51abc52108d3040817474fa8c85ab15020c12cb753515543c2d064143277155 postgres "/docker-entrypoint.sh postgres" 3 minutes ago Up 3 minutes 5432 /tcp my-first-postgres |
When you want to know what images you have available locally you can ask docker for that:
1 2 3 4 | [docker@centos7 ~]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE postgres latest 78e3985acac0 2 days ago 264.7 MB hello-world latest c54a2cc56cbb 5 months ago 1.848 kB |
How do you now connect to the PostgreSQL image?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [docker@centos7 ~]$ docker run -it -- rm --link my-first-postgres:postgres postgres psql -h postgres -U postgres Password for user postgres: psql (9.6.1) Type "help" for help. postgres= # l+ List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges | Size | Tablespace | Description -----------+----------+----------+------------+------------+-----------------------+---------+------------+-------------------------------------------- postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | 7063 kB | pg_default | default administrative connection database template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c /postgres +| 6953 kB | pg_default | unmodifiable empty database | | | | | postgres=CTc /postgres | | | template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c /postgres +| 6953 kB | pg_default | default template for new databases | | | | | postgres=CTc /postgres | | | (3 rows) |
Or to get bash:
1 2 3 4 5 6 7 8 9 10 | [docker@centos7 ~]$ docker run -it -- rm --link my-first-postgres:postgres postgres bash root@f8c3b3738336:/$ cat /etc/os-release PRETTY_NAME= "Debian GNU/Linux 8 (jessie)" NAME= "Debian GNU/Linux" VERSION_ID= "8" VERSION= "8 (jessie)" ID=debian HOME_URL= "http://www.debian.org/" SUPPORT_URL= "http://www.debian.org/support" BUG_REPORT_URL= "https://bugs.debian.org/" |
Ok, this PostgreSQL image is based on Debian 8. Lets say this is not what I like because I want my PostgreSQL image based on CentOS. This is the topic for the next post: We’ll build our own CentOS image and get deeper in what the stacked filesystem is about. Once we’ll have that available we’ll use that image to build a PostgreSQL image on top of that.