This blogpost is about how to install and run the Oracle database in docker. Please mind this is not an officially supported virtualisation platform for the Oracle database. This is a proof of concept setup.
Linux host setup.
In my setup, I used a linux host, freshly installed with Oracle Linux 6.7, which is going to be used as docker server. Please mind you need to leave diskspace (or a disk device) unused for the commonly documented docker setup with the btrfs driver. The root filesystem is using a ext4 filesystem by default. For the proof of concept setup, a 20G diskspace for the Operating system only is enough. I used the minimal linux installation.
The first step is to add a disk, or use a partition and format it with btrfs and mount it:
# lsblk # yum install btrfs-progs # mkfs.btrfs -L btrfs1 /dev/sdb # vi /etc/fstab: LABEL=btrfs1 /var/lib/docker btrfs defaults 0 1 # mkdir /var/lib/docker # mount -a
1- list blockdevices. this showed me my added block device for docker was /dev/sdb.
2- because of the minimal install, I need to add the btrfs userspace programs.
3- create a btrfs filesystem on /dev/sdb, and label it btrfs1.
4- edit /etc/fstab, make it mount the btrfs filesystem to /var/lib/docker.
5- create the mount point for the btrfs filesystem.
6- mount all the mounts listed in /etc/fstab.
The second step is to install and preliminary configure docker:
# vi /etc/yum.repos.d/public-yum-ol6.repo: enable public_ol6_addons # yum install docker # vi /etc/sysconfig/docker: add "-s btrfs" to other_args # service docker start # chkconfig docker on # printf "limit memlock unlimited unlimited\n" > /etc/init/docker.conf
1- enable public_ol6_addons, in which the docker rpm sits.
2- install docker.
3- make docker use the btrfs driver.
4- start docker.
5- enable autostart for docker.
6- reset the memlock ulimit for the containers (needed for the Oracle database SGA later on).
Now we can go into the true docker part of it. Docker lives, as far as I can see at this point, in the /var/lib/docker directory. In order to build my oracle database image, I create a ‘dockerfiles’ directory, and in it a ‘build-oracle-12102’ directory:
mkdir -p /var/lib/docker/dockerfiles/build-oracle-12102
Next up, the dockerfile (aptly named Dockerfile by default) needs to be made in the build-oracle-12102 directory to build an image:
FROM oraclelinux:6 MAINTAINER email@example.com RUN groupadd -g 54321 oinstall RUN groupadd -g 54322 dba RUN useradd -m -g oinstall -G oinstall,dba -u 54321 oracle RUN yum -y install oracle-rdbms-server-12cR1-preinstall perl wget unzip RUN mkdir /u01 RUN chown oracle:oinstall /u01 USER oracle WORKDIR /home/oracle ENV mosUserfirstname.lastname@example.org mosPass=supersecret DownList=1,2 RUN wget https://dl.dropboxusercontent.com/u/7787450/getMOSPatch.sh RUN wget https://dl.dropboxusercontent.com/u/7787450/responsefile_oracle12102.rsp RUN echo "226P;Linux x86-64" > /home/oracle/.getMOSPatch.sh.cfg RUN sh /home/oracle/getMOSPatch.sh patch=17694377 RUN unzip p17694377_121020_Linux-x86-64_1of8.zip RUN unzip p17694377_121020_Linux-x86-64_2of8.zip RUN rm p17694377_121020_Linux-x86-64_1of8.zip p17694377_121020_Linux-x86-64_2of8.zip RUN /home/oracle/database/runInstaller -silent -force -waitforcompletion -responsefile /home/oracle/responsefile_oracle12102.rsp -ignoresysprereqs -ignoreprereq USER root RUN /u01/app/oraInventory/orainstRoot.sh RUN /u01/app/oracle/product/184.108.40.206/dbhome_1/root.sh -silent RUN rm -rf /home/oracle/responsefile_oracle12102.rsp /home/oracle/getMOSPatch.sh /home/oracle/database USER oracle WORKDIR /home/oracle RUN mkdir -p /u01/app/oracle/data RUN wget https://dl.dropboxusercontent.com/u/7787450/manage-oracle.sh RUN chmod 700 /home/oracle/manage-oracle.sh RUN wget https://dl.dropboxusercontent.com/u/7787450/db_install.dbt EXPOSE 1521 CMD /home/oracle/manage-oracle.sh
1- FROM; this is the (operating system) image which is used as a base image. If it is not available locally, it will be pulled from the central docker repository. oraclelinux:6 means Oracle Linux version 6. This serves as the base image on which all the other things are built.
2- MAINTAINER; the maintainer of the new image, obviously.
3/4/5- RUN; the groups and user for the oracle database (oinstall, dba, oracle) are created. This looks like an redundant step, as this is supposed to be done by the oracle-dbms-server-12cR1-preinstall rpm. However, this does not work. By manually creating them this way, the assignment is alright.
6- RUN; yum -y installs all the necessary packages. Oddly, it seems unzip is not in the preinstall package?
7- RUN; /u01 is the directory in which the database software is installed.
8- RUN; chown user:group is used to change the user and group permissions of /u01.
9- USER; this changes the current user for execution to ‘oracle’.
10- WORKDIR; this sets the work directory.
11- ENV; this sets a few environment variables needed by the getMOSPatch.sh script. Please mind you need to fill out your own My Oracle Support username (mosUser) and password (mosPass). Leave DownList as it is.
12- RUN; this downloads a modified version of the getMOSPatch.sh script by Maris Elsins (thank you Maris and John Piwowar). The only modification I made is to make the downloads pre-selectable by setting the DownList environment variable.
13- RUN; this downloads a response file for the installation.
14- RUN; this echoes the language for the patches to download.
15- RUN; sh /home/oracle/getMOSPatch.sh patch=17694377 runs getMOSPatch.sh and downloads file 1 and 2 of patch 17694377, which is the patch for the Oracle database version 220.127.116.11.
16/17- RUN; unzips the two files as the result of the patch download in step 15.
18- RUN; cleanup the zip files after they have been extracted.
19- RUN; runs the installer with the template downloaded, and a few extra switches.
20- USER; here we switch to root, because after installation there are two scripts which need to be run as root.
21/22- RUN; runs the two post-install root scripts.
23- RUN; cleans up the getMOSPatch.sh script, the template and the database directory, which contains the database installation media.
24/25- USER/WORKDIR; change back to the oracle user.
26- RUN; create a directory on which we hook the persistent storage of the database
27- RUN; download manage-oracle.sh, the main script that sets up the listener and the database.
28- RUN; sets the mode of the manage-oracle.sh script to 700 (read/write/execute for the oracle user).
29- RUN; download the database installation template.
30- EXPOSE; make port 1521 (the listener port) available externally.
31- CMD; make /home/oracle/manage-oracle.sh the main command (pid 1 in the container).
After creating the Dockerfile, we can build an image with the description in the Dockerfile:
# cd /var/lib/docker/dockerfiles/build-oracle-12102 # docker build -t "oracle-12102" .
(mind the trailing dot)
This will take some time, as it is downloading the operating system base image, the software and does the installation. If all goes well, you end up with two images:
# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE oracle-12102 latest 28c18b083371 42 seconds ago 12.25 GB oraclelinux 6 cfc75fa9f295 11 days ago 156.2 MB
Which are the image we started with (oraclelinux), and our fresh created image “oracle-12102”.
In order to start the container, we need to prepare a directory for the database files, which is mounted into the container, and is persistent (so we can actually store and save everything, which is one of the basic things a database is used for). For this I create a directory, and set the userid and group id of the Oracle user (inside the container!):
# mkdir -p /var/lib/docker/db/frits # chown 54321:54321 /var/lib/docker/db/frits
Now that we made all preparations, we can start a container, in which the database will run. The way I created the script (manage-oracle.sh), the container will create or start a database with the hostname set for the container. At this point I don’t know if that is the best choice. It seems like a good way to automate currently. This is how a container is started based on the image we just created. The startup line looks rather long, because a couple of settings need to be made, which are saved by docker:
# docker run --ipc=host --volume=/var/lib/docker/db/frits:/u01/app/oracle/data --name frits --hostname frits --detach=true oracle-12102 25efb5d26aad31e7b06a8e2707af7c25943e2e42ec5c432dc9fa55f0da0bdaef
If you wonder what the long hexadecimal string is, this is the full image hash. Now the container is starting. There are a couple of important things to see in the docker run line:
–ipc=host This makes the host’s IPC available to the container. Otherwise my container only could see 32MB of shared memory.
–volume=/var/lib/docker/db/frits:/u01/app/oracle/data This maps the docker hosts directory /var/lib/docker/db/frits available in the container at /u01/app/oracle/data (–volume=host:container). This is persistent storage, unlike the container’s image.
–name=frits This gives the docker container the name frits.
–hostname=frits This makes the container set the hostname to frits.
–detach=true This makes the container not be connected to the starting shell, because otherwise the shell signals will be honoured by the container, meaning the CTRL-C will stop the container.
oracle-12102 This is the image on which the container is based.
If you want to see what is going on in the container, the first thing is to use the docker logs command. This will show the standard out of the main running process. The container will stop if the main running process terminates (CMD in our Dockerfile, which is starting manage-oracle.sh). In the manage-oracle.sh I put a tail on the alert.log of the database after starting the listener, so you can use ‘docker logs’ to look how the database is doing (-f means follow):
# docker logs -f frits LSNRCTL for Linux: Version 18.104.22.168.0 - Production on 11-AUG-2015 15:33:30 Copyright (c) 1991, 2014, Oracle. All rights reserved. Starting /u01/app/oracle/product/22.214.171.124/dbhome_1/bin/tnslsnr: please wait... TNSLSNR for Linux: Version 126.96.36.199.0 - Production System parameter file is /u01/app/oracle/product/188.8.131.52/dbhome_1/network/admin/listener.ora Log messages written to /u01/app/oracle/diag/tnslsnr/frits/listener/alert/log.xml Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=frits)(PORT=1521))) Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521))) Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=frits)(PORT=1521))) STATUS of the LISTENER ------------------------ Alias LISTENER Version TNSLSNR for Linux: Version 184.108.40.206.0 - Production Start Date 11-AUG-2015 15:33:30 Uptime 0 days 0 hr. 0 min. 5 sec Trace Level off Security ON: Local OS Authentication SNMP OFF Listener Parameter File /u01/app/oracle/product/220.127.116.11/dbhome_1/network/admin/listener.ora Listener Log File /u01/app/oracle/diag/tnslsnr/frits/listener/alert/log.xml Listening Endpoints Summary... (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=frits)(PORT=1521))) (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521))) The listener supports no services The command completed successfully SQL*Plus: Release 18.104.22.168.0 Production on Tue Aug 11 15:33:36 2015 Copyright (c) 1982, 2014, Oracle. All rights reserved. Connected to an idle instance. SQL> ORACLE instance started. Total System Global Area 1048576000 bytes Fixed Size 2932336 bytes Variable Size 247464336 bytes Database Buffers 658505728 bytes Redo Buffers 139673600 bytes Database mounted. Database opened. SQL> Disconnected from Oracle Database 12c Enterprise Edition Release 22.214.171.124.0 - 64bit Production With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options Database Characterset is WE8MSWIN1252 No Resource Manager plan active replication_dependency_tracking turned off (no async multimaster replication found) Starting background process AQPC Tue Aug 11 15:33:55 2015 AQPC started with pid=30, OS id=88 Starting background process CJQ0 Tue Aug 11 15:33:57 2015 CJQ0 started with pid=61, OS id=150 Completed: ALTER DATABASE OPEN Tue Aug 11 15:34:10 2015 =========================================================== Dumping current patch information =========================================================== No patches have been applied ===========================================================
If you followed the examples in the blogpost, this is not what you would see, because there isn’t a database. Instead, if you look with docker logs, you will see:
# docker logs -f frits LSNRCTL for Linux: Version 126.96.36.199.0 - Production on 11-AUG-2015 15:44:51 Copyright (c) 1991, 2014, Oracle. All rights reserved. Starting /u01/app/oracle/product/188.8.131.52/dbhome_1/bin/tnslsnr: please wait... TNSLSNR for Linux: Version 184.108.40.206.0 - Production System parameter file is /u01/app/oracle/product/220.127.116.11/dbhome_1/network/admin/listener.ora Log messages written to /u01/app/oracle/diag/tnslsnr/frits/listener/alert/log.xml Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=frits)(PORT=1521))) Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521))) Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=frits)(PORT=1521))) STATUS of the LISTENER ------------------------ Alias LISTENER Version TNSLSNR for Linux: Version 18.104.22.168.0 - Production Start Date 11-AUG-2015 15:44:51 Uptime 0 days 0 hr. 0 min. 5 sec Trace Level off Security ON: Local OS Authentication SNMP OFF Listener Parameter File /u01/app/oracle/product/22.214.171.124/dbhome_1/network/admin/listener.ora Listener Log File /u01/app/oracle/diag/tnslsnr/frits/listener/alert/log.xml Listening Endpoints Summary... (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=frits)(PORT=1521))) (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521))) The listener supports no services The command completed successfully Creating and starting Oracle instance 2% complete 3% complete 5% complete
Which is the dbca working to create a database for you.
If you wonder what is going on inside the container, you can create a shell in it using the ‘docker exec’ command. This can not be done if the container is stopped, only if the container is running:
# docker exec -ti frits bash [oracle@frits ~]$
Unlike an attached start, a docker exec bash session can exit without interrupting the container.
In order to see running containers, use docker ps:
# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 304d273d9424 oracle-12102:latest "/bin/sh -c /home/or 2 minutes ago Up 2 minutes 1521/tcp frits
The container can be stopped:
# docker stop frits frits
It is not visible anymore as running container with docker ps:
# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
However, you can see stopped containers when you add ‘-a’ to ps:
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 304d273d9424 oracle-12102:latest "/bin/sh -c /home/or 25 minutes ago Exited (0) 2 minutes ago frits
You can start a stopped container with docker start:
# docker start -a=false frits frits
If you want containers to start on boot, you must automate the docker start command on the operating system.
Please mind the exposed port on the container (1521, the listener) is local to the container, and not visible outside of the docker host. If you want to make the port visible outside of the docker host, you must publish it from the container to the host. In order to do that, you must redefine the container. This requires you to drop it first:
# docker stop frits frits # docker rm frits frits # docker run --ipc=host --volume=/var/lib/docker/db/frits:/u01/app/oracle/data --name frits --hostname frits --detach=true --publish=1521:1521 oracle-12102 84421ae6ee344df7d78aba5f0f5314894137f120a05b94870fcc0f805287b632
Because the database is on persistent storage, it can just startup again.