I have to setup a mongo replica set with docker-compose. For the replica set the containers have to know each other.
I tried in docker-compose.yml
dbreplicasetpart1:
image: mongo:2.6.8
expose:
- '27018'
links:
- replicasetpart2
- replicasetpart3
cap_add:
- NET_ADMIN
dbreplicasetpart2:
image: mongo:2.6.8
links:
- replicasetpart1
- replicasetpart3
expose:
- '27019'
cap_add:
- NET_ADMIN
...
I get an circular import message. But if I remove the back-link to dbreplicasetpart1 I can't ping from dbreplicasetpart2 to dbreplicasetpart1.
What is the solution?
Updated for Docker 1.10
Docker 1.10 allows the definition of networks within the compose file.
Here's the updated code
version: "2"
services:
replica1:
image: mongo:2.6.8
container_name: replica1
networks:
- my-net
ports:
- "27018"
environment:
REPLICA2_URL: "http://replica2:27019"
replica2:
image: mongo:2.6.8
container_name: replica2
networks:
- my-net
ports:
- "27019"
environment:
REPLICA1_URL: "http://replica1:27018"
networks:
my-net:
driver: bridge
Previous answer for Docker 1.9
As of Docker 1.9, the solution to this is to create a custom network and pass it to the docker-compose up
command.
Create a network
docker network create --driver bridge my-net
Reference that network as an environment variable (${NETWORK}
)in the docker-compose.yml files. Eg:
```
replica1:
image: mongo:2.6.8
container_name: replica1
net: ${NETWORK}
ports:
- "27018"
environment:
REPLICA2_URL: "http://replica2:27019"
replica2:
image: mongo:2.6.8
container_name: replica2
net: ${NETWORK}
ports:
- "27019"
environment:
REPLICA1_URL: "http://replica1:27018"
```
Note that replica1
in http://replica1:27018
will resolve to the ip address of the replica1 service (container). No need to hardcode ip addresses; An entry for replica1 is automatically added to the /etc/host of the replica2 container. Same goes for the replica1 container. Docker will add an entry for replica2 in its /etc/host file.
- Call docker-compose, passing it the network you created
NETWORK=my-net docker-compose up -d -f docker-compose.yml
I've created a bridge network above which only works within one node (host). Good for dev. If you need to get two nodes to talk to each other, you need to create an overlay network. Same principle though. You pass the network name to the docker-compose up command.
You should use the ambassador pattern:
https://docs.docker.com/engine/admin/ambassador_pattern_linking/
Basically you create an intermediate component that bridges both of them together. You can see an example that we use with Spring Cloud's Eureka discovery service:
ambassador:
image: cpuguy83/docker-grand-ambassador
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
command: "-name eureka_1 -name eureka2_1 "
eureka:
links:
- "ambassador:eureka2"
eureka2:
links:
- "ambassador:eureka"
For simplicity, I only copied the links
We figured out to use the solution with the ambassador. It is indeed the more comfortable solution.
The configuration that works for us:
amba1:
image: cpuguy83/docker-grand-ambassador
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
command: "-name cucumber_dbreplicasetpart1_1"
amba2:
image: cpuguy83/docker-grand-ambassador
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
command: "-name cucumber_dbreplicasetpart2_1"
amba3:
image: cpuguy83/docker-grand-ambassador
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
command: "-name cucumber_dbreplicasetpart3_1"
dbreplicasetpart1:
image: 'mongo:2.6.8'
hostname: dbreplicasetpart1
command: >
bash -c
'
mongod --fork --logpath mongo.log --smallfiles --replSet rs1
echo "
printjson(
rs.initiate(
{
_id : \"rs1\",
members : [
{_id : 0, host : \"dbreplicasetpart1:27017\"},
{_id : 1, host : \"dbreplicasetpart2:27017\"},
{_id : 2, host : \"dbreplicasetpart3:27017\"},
]
}
)
);
" | mongo;
tail -f mongo.log
'
links:
- "amba2:dbreplicasetpart2"
- "amba3:dbreplicasetpart3"
dbreplicasetpart2:
image: 'mongo:2.6.8'
hostname: dbreplicasetpart2
command: >
bash -c
'
mongod --fork --logpath mongo.log --smallfiles --replSet rs1
echo "
printjson(
rs.initiate(
{
_id : \"rs1\",
members : [
{_id : 0, host : \"dbreplicasetpart1:27017\"},
{_id : 1, host : \"dbreplicasetpart2:27017\"},
{_id : 2, host : \"dbreplicasetpart3:27017\"},
]
}
)
);
" | mongo;
tail -f mongo.log
'
links:
- "amba1:dbreplicasetpart1"
- "amba3:dbreplicasetpart3"
dbreplicasetpart3:
image: 'mongo:2.6.8'
hostname: dbreplicasetpart3
command: >
bash -c
'
mongod --fork --logpath mongo.log --smallfiles --replSet rs1
echo "
printjson(
rs.initiate(
{
_id : \"rs1\",
members : [
{_id : 0, host : \"dbreplicasetpart1:27017\"},
{_id : 1, host : \"dbreplicasetpart2:27017\"},
{_id : 2, host : \"dbreplicasetpart3:27017\"},
]
}
)
);
" | mongo;
tail -f mongo.log
'
links:
- "amba1:dbreplicasetpart1"
- "amba2:dbreplicasetpart2"
Here's what should still work in Docker 1.7.1 (in case you stuck with CentOS 6):
etcd:
image: elcolio/etcd:latest
skydns:
image: outrider/skydns
links:
- etcd
environment:
ETCD_MACHINES: "http://etcd:4001"
SKYDNS_DOMAIN: "docker"
SKYDNS_PATH_PREFIX: my
SKYDNS_NDOTS: 0
SKYDNS_VERBOSE: "true"
SKYDNS_ADDR: 0.0.0.0:53
expose:
- 53
my-service:
image: alpine
command: sh -c "dns_add my-service && ping my-service"
links:
- etcd
- skydns
dns_add
script:
#!/usr/bin/env sh
# This script configures resov.conf to use
# "skydns" name server with "docker" domain
# and adds a service name specified in the first argument
SERVICE_NAME=$1
waitforit () {
HOST=$1
PORT=$2
TIME_OUT=${3:-30};
END=$(($(date "+%s+$TIME_OUT")))
while [ $(date "+%s") -lt $END ]
do nc -z -w1 $HOST $PORT && break
done
return $END
}
# Use skydns to resolve names
echo "nameserver `resolveip -s skydns`" > /etc/resolv.conf
echo "search docker" >> /etc/resolv.conf
# Put yourself to DNS
ETCD_HOST=etcd
ETCD_PORT=4001
waitforit $ETCD_HOST $ETCD_PORT
HOST_IP=`resolveip -s $HOSTNAME`
apk update && apk add curl
curl -XPUT http://$ETCD_HOST:$ETCD_PORT/v2/keys/my/docker/$SERVICE_NAME -d value="{\"host\":\"$HOST_IP\"}"
Here's an explanation:
- We set up our own DNS server in a container
- We configure our containers to use that server
- We configure that DNS server using special HTTP requests