Skip to content

Latest commit

 

History

History
330 lines (266 loc) · 15.9 KB

ch05-compose.adoc

File metadata and controls

330 lines (266 loc) · 15.9 KB

Multi-container application using Docker Compose

Docker Compose is a tool for defining and running complex applications with Docker. With Compose, you define a multi-container application in a single file, then spin your application up in a single command which does everything that needs to be done to get it running.
— github.com/docker/compose

An application using Docker containers will typically consist of multiple containers. With Docker Compose, there is no need to write shell scripts to start your containers. All the containers are defined in a configuration file using services, and then docker-compose script is used to start, stop, and restart the application and all the services in that application, and all the containers within that service. The complete list of commands is:

Command Purpose

build

Build or rebuild services

help

Get help on a command

kill

Kill containers

logs

View output from containers

port

Print the public port for a port binding

ps

List containers

pull

Pulls service images

restart

Restart services

rm

Remove stopped containers

run

Run a one-off command

scale

Set number of containers for a service

start

Start services

stop

Stop services

up

Create and start containers

The application used in this section will show how to query a Couchbase sample data using simple Java EE application deployed on WildFly. The Java EE application will use JAX-RS to publish REST endpoint which will then be invoked using curl.

WildFly and Couchbase will be running in two separate containers, and thus making this a multi-container application.

Configuration File

Th entry point to Docker Compose is a Compose file, usually called docker-compose.yml. Create a new directory javaee. In that directory, create a new file docker-compose.yml in it. Use the following contents:

version: '3.3'
services:
  web:
    image: arungupta/couchbase-javaee:travel
    environment:
      - COUCHBASE_URI=db
    ports:
      - 8080:8080
      - 9990:9990
    depends_on:
      - db
  db:
    image: arungupta/couchbase:travel
    ports:
      - 8091:8091
      - 8092:8092
      - 8093:8093
      - 11210:11210

In this Compose file:

  1. Two services in this Compose are defined by the name db and web attributes

  2. Image name for each service defined using image attribute

  3. The arungupta/couchbase:travel image starts Couchbase server, configures it using Couchbase REST API, and loads a sample bucket

  4. The arungupta/couchbase-javaee:travel image starts WildFly and deploys application WAR file built from https://github.com/arun-gupta/couchbase-javaee. Clone that project if you want to build your own image.

  5. environment attribute defines environment variables accessible by the application deployed in WildFly. COUCHBASE_URI refers to the database service. This is used in the application code as shown at https://github.com/arun-gupta/couchbase-javaee/blob/master/src/main/java/org/couchbase/sample/javaee/Database.java.

  6. Port forwarding is achieved using ports attribute

  7. depends_on attribute allows to express dependency between services. In this case, Couchbase will be started before WildFly. Application-level health are still user’s responsibility.

Start Application

All services in the applicaiton can be started, in detached mode, by giving the command:

docker-compose up -d

An alternate Compose file name can be specified using -f option.

An alternate directory where the compose file exists can be specified using -p option.

This shows the output as:

Creating javaee_db_1 ...
Creating javaee_db_1 ... done
Recreating javaee_web_1 ...
Recreating javaee_web_1 ... done

The output may differ slightly if the images are downloaded as well.

Started services can be verified using the command docker-compose ps:

    Name                  Command               State                                 Ports
-------------------------------------------------------------------------------------------------------------------------
javaee_db_1    /entrypoint.sh /opt/couchb ...   Up      11207/tcp, 0.0.0.0:11210->11210/tcp, 11211/tcp, 18091/tcp,
                                                        18092/tcp, 18093/tcp, 0.0.0.0:8091->8091/tcp,
                                                        0.0.0.0:8092->8092/tcp, 0.0.0.0:8093->8093/tcp, 8094/tcp
javaee_web_1   /opt/jboss/wildfly/bin/sta ...   Up      0.0.0.0:8080->8080/tcp, 0.0.0.0:9990->9990/tcp

This provides a consolidated view of all the services, and containers within each of them.

Alternatively, the containers in this application, and any additional containers running on this Docker host can be verified by using the usual docker container ls command:

CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS              PORTS                                                                                                         NAMES
b96193760e34        arungupta/couchbase-javaee:travel   "/opt/jboss/wildfl..."   7 minutes ago       Up 7 minutes        0.0.0.0:8080->8080/tcp, 0.0.0.0:9990->9990/tcp                                                                javaee_web_1
4a8451affcef        arungupta/couchbase:travel          "/entrypoint.sh /o..."   7 minutes ago       Up 7 minutes        8094/tcp, 0.0.0.0:8091-8093->8091-8093/tcp, 11207/tcp, 11211/tcp, 0.0.0.0:11210->11210/tcp, 18091-18093/tcp   javaee_db_1

Service logs can be seen using docker-compose logs command, and looks like:

Attaching to javaee_web_1, javaee_db_1
web_1  | =========================================================================
web_1  |
web_1  |   JBoss Bootstrap Environment
web_1  |
web_1  |   JBOSS_HOME: /opt/jboss/wildfly
web_1  |


. . .

db_1   | ++ /entrypoint.sh couchbase-server
db_1   | Starting Couchbase Server -- Web UI available at http://<ip>:8091 and logs available in /opt/couchbase/var/lib/couchbase/logs
web_1  | 00:40:46,265 INFO  [org.jboss.as.ejb3.deployment] (MSC service thread 1-2) WFLYEJB0473: JNDI bindings for session bean named 'Database' in deployment unit 'deployment "airlines.war"' are as follows:
db_1   | ++ curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=300 -d indexMemoryQuota=300

. . .

web_1  | 00:41:03,960 INFO  [stdout] (ServerService Thread Pool -- 64) Trying to connect to the database
web_1  | 00:41:03,981 INFO  [com.couchbase.client.core.node.Node] (cb-io-1-3) Connected to Node db
web_1  | 00:41:04,215 INFO  [com.couchbase.client.core.config.ConfigurationProvider] (cb-computations-4) Opened bucket travel-sample
web_1  | 00:41:04,251 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for travel-sample bucket) ...
web_1  | 00:41:07,252 INFO  [stdout] (ServerService Thread Pool -- 64) Bucket found!
web_1  | 00:41:07,267 INFO  [stdout] (ServerService Thread Pool -- 64) Query service not up ...
web_1  | 00:41:07,267 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for Query service or bucket to be loaded) ...
web_1  | 00:41:10,358 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for Query service or bucket to be loaded) ...

. . .

web_1  | 00:41:25,464 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for Query service or bucket to be loaded) ...
web_1  | 00:41:28,466 INFO  [stdout] (ServerService Thread Pool -- 64) 31591 number of JSON documents in bucket.
web_1  | 00:41:28,491 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for indexes) ...
web_1  | 00:41:31,496 INFO  [stdout] (ServerService Thread Pool -- 64) Sleeping for 3 secs (waiting for indexes) ...
web_1  | 00:41:35,999 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 64) RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.couchbase.sample.javaee.MyApplication

. . .

web_1  | 00:41:36,090 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 64) WFLYUT0021: Registered web context: /airlines
web_1  | 00:41:36,152 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 34) WFLYSRV0010: Deployed "airlines.war" (runtime-name : "airlines.war")
web_1  | 00:41:36,351 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
web_1  | 00:41:36,359 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
web_1  | 00:41:36,360 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 10.1.0.Final (WildFly Core 2.2.0.Final) started in 57412ms - Started 443 of 691 services (404 services are lazy, passive or on-demand)

depends_on attribute in Compose definition file ensures the container start up order. But application-level start up needs to be ensured by the applications running inside container. In our case, WildFly starts up rather quickly but takes a few seconds for the database to start up. This means the Java EE application deployed in WildFly is not able to communicate with the database. This outlines a best practice when building micro services applications: you must code defensively and ensure in your application initialization that the micro services you depend on have started, without assuming startup order. This is shown in the database initialization code at https://github.com/arun-gupta/couchbase-javaee/blob/master/src/main/java/org/couchbase/sample/javaee/Database.java. It performs the following checks:

  1. Bucket exists

  2. Query service of Couchbase is up and running

  3. Sample bucket is fully loaded

Verify Application

Now that the WildFly and Couchbase servers have been configured, let’s access the application. You need to specify IP address of the host where WildFly is running (localhost in our case).

The endpoint can be accessed in this case as:

curl -v http://localhost:8080/airlines/resources/airline

The output is shown as:

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /airlines/resources/airline HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Server: WildFly/10
< Content-Type: application/octet-stream
< Content-Length: 1402
< Date: Sat, 23 Sep 2017 00:52:03 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
[{"travel-sample":{"country":"United States","iata":"Q5","callsign":"MILE-AIR","name":"40-Mile Air","icao":"MLA","id":10,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"TQ","callsign":"TXW","name":"Texas Wings","icao":"TXW","id":10123,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"A1","callsign":"atifly","name":"Atifly","icao":"A1F","id":10226,"type":"airline"}}, {"travel-sample":{"country":"United Kingdom","iata":null,"callsign":null,"name":"Jc royal.britannica","icao":"JRB","id":10642,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"ZQ","callsign":"LOCAIR","name":"Locair","icao":"LOC","id":10748,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"K5","callsign":"SASQUATCH","name":"SeaPort Airlines","icao":"SQH","id":10765,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"KO","callsign":"ACE AIR","name":"Alaska Central Express","icao":"AER","id":109,"type":"airline"}}, {"travel-sample":{"country":"United Kingdom","iata":"5W","callsign":"FLYSTAR","name":"Astraeus","icao":"AEU","id":112,"type":"airline"}}, {"travel-sample":{"country":"France","iata":"UU","callsign":"REUNION","name":"Air Austral","icao":"REU","id":1191,"type":"airline"}}, {"travel-sample":{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","id":1203,"type":"airline"}}]

This shows 10 airlines from the travel-sample bucket.

A single resource can be obtained:

curl -v http://localhost:8080/airlines/resources/airline/137

It shows the output:

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /airlines/resources/airline/137 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Server: WildFly/10
< Content-Type: application/octet-stream
< Content-Length: 131
< Date: Sat, 23 Sep 2017 00:52:30 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"travel-sample":{"country":"France","iata":"AF","callsign":"AIRFRANS","name":"Air France","icao":"AFR","id":137,"type":"airline"}}

A new resource can be created:

curl -v -H "Content-Type: application/json" -X POST -d '{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","type":"airline"}' http://localhost:8080/airlines/resources/airline
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /airlines/resources/airline HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 104
>
* upload completely sent off: 104 out of 104 bytes
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Server: WildFly/10
< Content-Type: application/octet-stream
< Content-Length: 117
< Date: Sat, 23 Sep 2017 00:52:55 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","id":"19810","type":"airline"}

The output shows the id of the newly created resource.

Let’s update this resource using the id:

curl -v -H "Content-Type: application/json" -X PUT -d '{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","type":"airline","id": "19810"}' http://localhost:8080/airlines/resources/airline/19810

The only change is name from "AirlineAir" to "Airlin Air".

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /airlines/resources/airline/19810 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 119
>
* upload completely sent off: 119 out of 119 bytes
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Server: WildFly/10
< Content-Type: application/octet-stream
< Content-Length: 118
< Date: Sat, 23 Sep 2017 00:53:26 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","id":"19810","type":"airline"}

Let’s delete the created resource:

curl -v -X DELETE http://localhost:8080/airlines/resources/airline/19810
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /airlines/resources/airline/19810 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Server: WildFly/10
< Content-Type: application/octet-stream
< Content-Length: 136
< Date: Sat, 23 Sep 2017 00:53:53 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
{"travel-sample":{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","id":"19810","type":"airline"}}

Shutdown Application

Shutdown the application using docker-compose down:

Stopping javaee_web_1 ... done
Stopping javaee_db_1  ... done
Removing javaee_web_1 ... done
Removing javaee_db_1  ... done
Removing network javaee_default

This stops the container in each service and removes all the services. It also deletes any networks that were created as part of this application.