[[geode-docker]]
== Docker
:docker-docs-url: https://docs.docker.com
:docker-site-url: https://www.docker.com/
:geode-name: Apache Geode
:testcontainers-url: https://www.testcontainers.org


The state of modern software application development is moving towards https://www.docker.com/resources/what-container[_containerization_].
Containers offer a controlled environment to predictably build (configure & package), run and manage your applications
in a reliable and repeatable manner regardless of context. The intrinsic benefit of using Containers is a no brainer.

Understandably, {docker-site-url}[Docker's] popularity took off like wildfire given its highly powerful and simplified
model for creating, using and managing Containers to run packaged applications.

Docker's ecosystem is also quite impressive, with the event of {testcontainers-url}[Testcontainers]
along with Spring Boot's now {spring-boot-docs-html}/spring-boot-features.html#building-docker-images[dedicated support]
to create packaged Spring Boot apps in {docker-docs-url}/get-started/overview/#docker-objects[Docker Images]
that are then later run in a Docker Container.

TIP: Also see {spring-boot-docs-html}/deployment.html#containers-deployment[Deploying to Containers] to learn more.

{geode-name} is no exception to being able to run in a controlled, containerized environment. The goal of this chapter
is to get you started running {geode-name} in a Container and interfacing to a containerized {geode-name} cluster from
your Spring Boot, {geode-name} client applications.

This chapter does not cover how to run your Spring Boot, {geode-name} client applications in a Container since that is
already covered by Spring Boot (again, see {spring-boot-docs-html}/spring-boot-features.html#building-docker-images[here]
and {spring-boot-docs-html}/deployment.html#containers-deployment[here], along with Docker's {docker-docs-url}/get-started/overview/[docs]).
Instead, our focus is on how to run an {geode-name} cluster in a Container and connect to it from a Spring Boot,
{geode-name} client application, regardless of whether the app is running in a Container or not.

Let's get started.

[[geode-docker-image]]
=== Acquiring the {geode-name} Docker Image

To run an {geode-name} cluster inside a Docker Container you must first acquire the Docker Image. The {geode-name}
Docker Image can be acquired from https://hub.docker.com/r/apachegeode/geode/[Docker Hub].

While {geode-name}'s official {apache-geode-docs}[documentation] is less than clear on how to use {geode-name} in Docker,
we find a bit of relief in the {apache-geode-wiki}/How+to+use+Geode+on+Docker[Wiki]. However, for a complete and
comprehensive write up, please refer to the instructions in the https://github.com/markito/geode-docker#building-the-container-image[README]
from this https://github.com/markito/geode-docker[GitHub Repo].

NOTE: You must have {docker-docs-url}/get-docker[Docker] installed on your local system to complete the following steps.

Effectively, the high-level steps are as follows:

1) Acquire the {geode-name} Docker Image from Docker Hub using the `docker pull` command from the command-line:

.Download/Install the {geode-name} Docker Image
[source,text]
----
$ docker pull apachegeode/geode
Using default tag: latest
latest: Pulling from apachegeode/geode
Digest: sha256:6a6218f22a2895bb706175727c7d76f654f9162acac22b2d950d09a2649f9cf4
Status: Image is up to date for apachegeode/geode:latest
docker.io/apachegeode/geode:latest
----

Instead of pulling from the `nightly` TAG as suggested, the Spring team highly recommends that you pull from the
`latest` TAG, which pulls a stable, production-ready {geode-name} Docker Image based on the latest {geode-name}
GA version.

2) Verify the {geode-name} Docker Image was downloaded and installed successfully:

[source,text]
----
$ docker image ls
REPOSITORY                       TAG                          IMAGE ID            CREATED             SIZE
apachegeode/geode                latest                       a2e210950712        2 months ago        224MB
cloudfoundry/run                 base-cnb                     3a7d172559c2        8 weeks ago         71.2MB
open-liberty                     19.0.0.9-webProfile8         dece75feff1a        3 months ago        364MB
tomee                            11-jre-8.0.0-M3-webprofile   0d03e4d395e6        3 months ago        678MB
...
----

Now, you are ready to run {geode-name} in a Docker Container.

[[geode-docker-container]]
=== Running {geode-name} in a Docker Container

Now that we have acquired the {geode-name} Docker Image, we can run {geode-name} in a Docker Container. Use the
following `docker run` command to start {geode-name} in a Docker Container:

.Start the {geode-name} Docker Container
[source,text]
----
$ docker run -it -p 10334:10334 -p 40404:40404 -p 1099:1099 -p 7070:7070 -p 7575:7575 apachegeode/geode
    _________________________     __
   / _____/ ______/ ______/ /____/ /
  / /  __/ /___  /_____  / _____  /
 / /__/ / ____/  _____/ / /    / /
/______/_/      /______/_/    /_/    1.12.0

Monitor and Manage Apache Geode
gfsh>
----

Since the {geode-name} Docker Container was started in interactive mode, you must open a separate command-line shell
to verify the {geode-name} Docker Container is in fact running:

.Verify the {geode-name} Docker Container is Running
[source,text]
----
$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                                                                                                                  NAMES
3b30b9ffc5dc        apachegeode/geode   "gfsh"              44 seconds ago      Up 43 seconds       0.0.0.0:1099->1099/tcp, 0.0.0.0:7070->7070/tcp, 0.0.0.0:7575->7575/tcp, 0.0.0.0:10334->10334/tcp, 0.0.0.0:40404->40404/tcp, 8080/tcp   awesome_khorana

----

Of course, we know that the {geode-name} Docker Container is running since we ended up at a _Gfsh_ command prompt in the
interactive shell.

We also mapped ports between the Docker Container and the host system, exposing well-known ports used by {geode-name}
server-side, cluster processes, such as Locators and Cache Servers.

.{geode-name} Ports
[width=30%, options="header", cols="2,1"]
|===
| Process | Port

| HTTP    | 7070

| Locator | 10334

| Manager | 1099

| Server  | 40404

|===

It is unfortunate that the {geode-name} Docker Image only gives you a _Gfsh_ command prompt, leaving you with the task
of provisioning a cluster. It would have been more useful to provide preconfigured Docker Images with different
{geode-name} cluster configurations, such as 1 Locator + 1 Server, or 2 Locators + 4 Servers, etc. But, no matter, we
can start the cluster ourselves.

[[geode-docker-cluster]]
=== Start an {geode-name} Cluster in Docker

From inside the {geode-name} Docker Container we can start a Locator and a Server.

.Start {geode-name} Locator & Server
[source, text]
----
gfsh>start locator --name=LocatorOne --log-level=config --hostname-for-clients=localhost
Starting a Geode Locator in /LocatorOne...
.........
Locator in /LocatorOne on 3b30b9ffc5dc[10334] as LocatorOne is currently online.
Process ID: 167
Uptime: 9 seconds
Geode Version: 1.12.0
Java Version: 1.8.0_212
Log File: /LocatorOne/LocatorOne.log
JVM Arguments: -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.log-level=config -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /geode/lib/geode-core-1.12.0.jar:/geode/lib/geode-dependencies.jar

Successfully connected to: JMX Manager [host=3b30b9ffc5dc, port=1099]

Cluster configuration service is up and running.


gfsh>start server --name=ServerOne --log-level=config --hostname-for-clients=localhost
Starting a Geode Server in /ServerOne...
.......
Server in /ServerOne on 3b30b9ffc5dc[40404] as ServerOne is currently online.
Process ID: 267
Uptime: 7 seconds
Geode Version: 1.12.0
Java Version: 1.8.0_212
Log File: /ServerOne/ServerOne.log
JVM Arguments: -Dgemfire.default.locators=172.17.0.2[10334] -Dgemfire.start-dev-rest-api=false -Dgemfire.use-cluster-configuration=true -Dgemfire.log-level=config -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /geode/lib/geode-core-1.12.0.jar:/geode/lib/geode-dependencies.jar


gfsh>list members
Member Count : 2

   Name    | Id
---------- | --------------------------------------------------------------
LocatorOne | 172.17.0.2(LocatorOne:167:locator)<ec><v0>:41000 [Coordinator]
ServerOne  | 172.17.0.2(ServerOne:267)<v1>:41001


gfsh>describe member --name=LocatorOne
Name        : LocatorOne
Id          : 172.17.0.2(LocatorOne:167:locator)<ec><v0>:41000
Host        : 3b30b9ffc5dc
Regions     :
PID         : 167
Groups      :
Used Heap   : 50M
Max Heap    : 443M
Working Dir : /LocatorOne
Log file    : /LocatorOne/LocatorOne.log
Locators    : 172.17.0.2[10334]


gfsh>describe member --name=ServerOne
Name        : ServerOne
Id          : 172.17.0.2(ServerOne:267)<v1>:41001
Host        : 3b30b9ffc5dc
Regions     :
PID         : 267
Groups      :
Used Heap   : 77M
Max Heap    : 443M
Working Dir : /ServerOne
Log file    : /ServerOne/ServerOne.log
Locators    : 172.17.0.2[10334]

Cache Server Information
Server Bind              :
Server Port              : 40404
Running                  : true

Client Connections : 0

----

We now have an {geode-name} cluster running with 1 Locator and 1 Server inside a Docker Container. We deliberately
started the cluster with a minimal configuration. For example, we have no Regions in which to store data:

[source,text]
----
gfsh>list regions
No Regions Found

----

But, that is OK. Once more, we want to showcase the full power of SBDG and let the Spring Boot application drive
the configuration of the {geode-name} cluster running in the Docker Container as required by the application.

Let's have a quick look at our Spring Boot application.

[[geode-docker-application]]
=== Spring Boot, {geode-name} client application explained

The Spring Boot, {geode-name} `ClientCache` application we will use to connect to our {geode-name} cluster running in
the Docker Container appears as follows:

.Spring Boot, {geode-name} Docker client application
[source,java]
----
include::{docs-src-dir}/org/springframework/geode/docs/example/app/docker/SpringBootApacheGeodeDockerClientCacheApplication.java[tags=class]
----

Our `Customer` application domain model object type is defined as:

.`Customer` class
[source,java]
----
@Region("Customers")
class Customer {

	@Id
	private Long id;

	private String name;

}
----

And, we define a Spring Data CRUD _Repository_ to persist and access `Customers` stored in the "/Customers" Region:

.`CustomerRepository` interface
[source,java]
----
interface CustomerRepository extends CrudRepository<Customer, Long> {

	Customer findByNameLike(String name);

}
----

Our main class is annotated with `@SpringBootApplication` making it a proper Spring Boot application.

We additionally annotate the main class with SBDG's `@EnableClusterAware` to automatically detect the {geode-name}
cluster running in the Docker Container as well as to push cluster configuration metadata from the application to
the cluster as required by the application.

Specifically, the application requires that a Region called "Customers", as defined by the `@Region` mapping annotation
on the `Customer` application domain model class, exists on the server(s) in the cluster to persist `Customer` data.

We use the SDG `@EnableEntityDefinedRegions` annotation to define the matching, client `PROXY` "Customers" Region.

Optionally, we have also annotated our main class with SBDG's `@UseMemberName` annotation to give the `ClientCache`
a name, which we assert in the `assertClientCacheAndConfigureMappingPdxSerializer(:ClientCache)` method.

The primary work performed by this application is done in the Spring Boot `ApplicationRunner` bean definition. We
essentially create a `Customer` instance, "Jon Doe", save "Jon Doe" to the "Customers" Region managed by the server(s)
in the cluster, and then query for "Jon Doe" using OQL, asserting that the result is equal to the expected.

We log the output from the application's operations to see the application in action.

[[geode-docker-application-run]]
=== Running the Spring Boot, {geode-name} client application

When you run the Spring Boot, {geode-name} client application, you should see output similar to:

.Application log output
[source,text]
----
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java ...
    org.springframework.geode.docs.example.app.docker.SpringBootApacheGeodeDockerClientCacheApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

Saving Customer [Customer(name=Jon Doe)]...
Querying for Customer [SELECT * FROM /Customers WHERE name LIKE '%Doe']...
Customer was [Customer(name=Jon Doe)]

Process finished with exit code 0
----

Now when we review the configuration of the cluster, we see that the "/Customers" Region was created
once the application has run:

./Customers Region Configuration
[source,text]
----
gfsh>list regions
List of regions
---------------
Customers


gfsh>describe region --name=/Customers
Name            : Customers
Data Policy     : partition
Hosting Members : ServerOne

Non-Default Attributes Shared By Hosting Members

 Type  |    Name     | Value
------ | ----------- | ---------
Region | size        | 1
       | data-policy | PARTITION

----

Our "/Customers" Region contains a value, "Jon Doe", and we can verify this by running the following OQL Query
with _Gfsh_:

.Query the "/Customers" Region
[source,text]
----
gfsh>query --query="SELECT customer.name FROM /Customers customer"
Result : true
Limit  : 100
Rows   : 1

Result
-------
Jon Doe

----

Indeed, our application ran successfully!

[[geode-docker-application-conclusion]]
=== Conclusion

In this chapter, we saw how to connect a Spring Boot, {geode-name} `ClientCache` application to an {geode-name} cluster
running in a Docker Container.

Later, we will provide more information on how to scale up, or rather scale out, our {geode-name} cluster running in
Docker. Additionally, we will provide details on how you can use {geode-name}'s Docker Image with **Testcontainers**
when writing _Integration Tests_, which will formally become part of the Spring Test for {geode-name} (STDG) project.
