I am struggling with MongoDb 6 Replica Set and automated testing in CI/CD.
I have created a single docker image with a Replica Set for development and testing purposes.
This works well when running the docker container locally and running my tests against it. See the repository https://gitlab.com/sunnyatticsoftware/sandbox/mongo-rs And see the docker image in the public repository registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
Basically it has a dockerfile
FROM mongo:6.0.5-jammy as base
COPY ./init-mongodbs.sh ./init-replica.sh ./entry-point.sh /
RUN chmod +x /init-mongodbs.sh && \
chmod +x /init-replica.sh && \
chmod +x /entry-point.sh
# Data directory
ARG DB1_DATA_DIR=/var/lib/mongo1
ARG DB2_DATA_DIR=/var/lib/mongo2
ARG DB3_DATA_DIR=/var/lib/mongo3
# Log directory
ARG DB1_LOG_DIR=/var/log/mongodb1
ARG DB2_LOG_DIR=/var/log/mongodb2
ARG DB3_LOG_DIR=/var/log/mongodb3
# DB Ports
ARG DB1_PORT=27017
ARG DB1_PORT=27018
ARG DB1_PORT=27019
RUN mkdir -p ${DB1_DATA_DIR} && \
mkdir -p ${DB1_LOG_DIR} && \
mkdir -p ${DB2_DATA_DIR} && \
mkdir -p ${DB2_LOG_DIR} && \
mkdir -p ${DB3_DATA_DIR} && \
mkdir -p ${DB3_LOG_DIR} && \
chown `whoami` ${DB1_DATA_DIR} && \
chown `whoami` ${DB1_LOG_DIR} && \
chown `whoami` ${DB2_DATA_DIR} && \
chown `whoami` ${DB2_LOG_DIR} && \
chown `whoami` ${DB3_DATA_DIR} && \
chown `whoami` ${DB3_LOG_DIR}
EXPOSE ${DB1_PORT}
EXPOSE ${DB2_PORT}
EXPOSE ${DB3_PORT}
ENTRYPOINT [ "bash", "entry-point.sh" ]
and it copies some scripts that run when executing the container
The entry-point.sh
#!/bin/bash
/bin/bash ./init-replica.sh &
/bin/bash ./init-mongodbs.sh
The init-mongodbs.sh
#!/bin/bash
# Data directory
DB1_DATA_DIR="/var/lib/mongo1"
DB2_DATA_DIR="/var/lib/mongo2"
DB3_DATA_DIR="/var/lib/mongo3"
# Log directory
DB1_LOG_DIR="/var/log/mongodb1"
DB2_LOG_DIR="/var/log/mongodb2"
DB3_LOG_DIR="/var/log/mongodb3"
REPLICA_SET="${REPLICA_SET_NAME:-rs0}"
mongod --dbpath ${DB1_DATA_DIR} --logpath ${DB1_LOG_DIR}/mongod.log --fork --port 27017 --bind_ip_all --replSet $REPLICA_SET
mongod --dbpath ${DB2_DATA_DIR} --logpath ${DB2_LOG_DIR}/mongod.log --fork --port 27018 --bind_ip_all --replSet $REPLICA_SET
mongod --dbpath ${DB3_DATA_DIR} --logpath ${DB3_LOG_DIR}/mongod.log --port 27019 --bind_ip_all --replSet $REPLICA_SET
And the init-replica.sh
#!/bin/bash
DB1_PORT=27017
DB2_PORT=27018
DB3_PORT=27019
LOCAL_HOST="${HOST:-localhost}"
REPLICA_SET="${REPLICA_SET_NAME:-rs0}"
SLEEP_INITIATE="${DELAY_INITIATE:-30}"
RS_MEMBER_1="{ \"_id\": 0, \"host\": \"${LOCAL_HOST}:${DB1_PORT}\", \"priority\": 2 }"
RS_MEMBER_2="{ \"_id\": 1, \"host\": \"${LOCAL_HOST}:${DB2_PORT}\", \"priority\": 0 }"
RS_MEMBER_3="{ \"_id\": 2, \"host\": \"${LOCAL_HOST}:${DB3_PORT}\", \"priority\": 0 }"
echo "Waiting ${SLEEP_INITIATE} seconds before initiating replica set"
sleep ${SLEEP_INITIATE}
mongosh --eval "rs.initiate({ \"_id\": \"${REPLICA_SET}\", \"members\": [${RS_MEMBER_1}, ${RS_MEMBER_2}, ${RS_MEMBER_3}] });"
echo "Replica set initiated"
echo "$(mongosh --eval "rs.status()")"
so that it can be run as
version: '3.8'
services:
mongors:
image: registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
container_name: mongors
environment:
HOST: mongors
DELAY_INITIATE: 40
ports:
- 27017:27017
- 27018:27018
- 27019:27019
Notice it accepts an environment variable HOST
which I can use to give an alias mongors
so that in GitLab CI/CD I can refer to that hostname.
My app connection string could be something like this
mongodb://mongors:27017,mongors:27018,mongors:27019/?replicaSet=rs0&readPreference=primary&ssl=false
As I said, this works well when running the replica set as a docker container locally (see the docker compose).
When it runs in GitLab CI/CD it timesout. I use shared runners from GitLab.com, the built-in.
Running with gitlab-runner 16.1.0~beta.5.gf131a6a2 (f131a6a2)
Yet the connectivity seem to work fine
$ nc -zv mongors 27017
Connection to mongors (172.17.0.4) 27017 port [tcp/*] succeeded!
$ nc -zv mongors 27018
Connection to mongors (172.17.0.4) 27018 port [tcp/*] succeeded!
$ nc -zv mongors 27019
Connection to mongors (172.17.0.4) 27019 port [tcp/*] succeeded!
So connectivity is OK but I get exceptions from my app like this
System.TimeoutException: A timeout occurred after 30000ms selecting a server using CompositeServerSelector{ Selectors = MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector, LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }, OperationsCountServerSelector }. Client view of cluster state is { ClusterId : "1", ConnectionMode : "ReplicaSet", Type : "ReplicaSet", State : "Connected", Servers : [{ ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/mongors:27017" }", EndPoint: "Unspecified/mongors:27017", ReasonChanged: "Heartbeat", State: "Connected", ServerVersion: 6.0.0, TopologyVersion: { "processId" : ObjectId("646f9b54fb0fbb72cb9a3b70"), "counter" : NumberLong(0) }, Type: "ReplicaSetGhost", WireVersionRange: "[0, 17]", LastHeartbeatTimestamp: "2023-05-25T17:36:03.9519134Z", LastUpdateTimestamp: "2023-05-25T17:36:03.9519144Z" }] }.
Suggesting that there is a ReplicaSetGhost
or something wrong with the replica set.
Any idea why in GitLab CI/CD I cannot have my automated integration tests connect to the connection string using the service alias which matches my HOST
env variable?
At some point I saw in traces this
MongoServerError: No host described in new configuration with {version: 1, term: 0} for replica set rs0 maps to this node
So maybe the replica set internally does not understand the hostname mongors
?
UPDATE 1 (25 May): I created a sample repo with a sample pipeline that uses the standalone mongoDB single instance and also the image mongo-rs I created, to test connectivity.
https://gitlab.com/sunnyatticsoftware/sandbox/mongo-rs-tester
image: ubuntu:22.04
stages:
- test
test_mongors:
stage: test
services:
- name: registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
alias: mongors
variables:
# MongoDB
HOST: "mongors"
DELAY_INITIATE: "50"
before_script:
- apt-get update
- apt-get install -y curl
- apt-get install -y gnupg
- curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | tee /etc/apt/trusted.gpg.d/mongodb.asc > /dev/null
- echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
- apt-get update
- apt-get install -y mongodb-mongosh
- sleep 30
script: |
output=$(mongosh --host mongors:27017 --eval "db.stats()" 2>&1); if [ $? -eq 0 ]; then echo "OK"; else echo "ERROR: $output"; fi
test_mongo:
stage: test
services:
- name: mongo:6.0.5-jammy
alias: mongostandalone
variables:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: dummy
before_script:
- apt-get update
- apt-get install -y curl
- apt-get install -y gnupg
- curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | tee /etc/apt/trusted.gpg.d/mongodb.asc > /dev/null
- echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
- apt-get update
- apt-get install -y mongodb-mongosh
script: |
output=$(mongosh --host mongostandalone:27017 --username root --password dummy --eval "db.stats()" 2>&1); if [ $? -eq 0 ]; then echo "OK"; else echo "ERROR: $output"; fi
UPDATE 2 (29 May): Adding the IP to the /etc/hosts didn't make any difference.
image: ubuntu:22.04
stages:
- test
test_mongors:
stage: test
services:
- name: registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
alias: mongors
variables:
# MongoDB
HOST: "mongors"
DELAY_INITIATE: "50"
before_script:
- apt-get update
- apt-get install -y curl
- apt-get install -y gnupg
- apt-get install -y dnsutils
- MONGORS_ALIAS="mongors"
- MONGORS_IP_ADDRESS=$(getent hosts $MONGORS_ALIAS | awk '{ print $1 }')
- echo "$MONGORS_IP_ADDRESS $MONGORS_ALIAS" >> /etc/hosts
- echo "MongoDB IP $MONGORS_IP_ADDRESS added"
- cat /etc/hosts
- curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | tee /etc/apt/trusted.gpg.d/mongodb.asc > /dev/null
- echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
- apt-get update
- apt-get install -y mongodb-mongosh
- sleep 30
script: |
output=$(mongosh --host mongors:27017 --eval "db.stats()" 2>&1); if [ $? -eq 0 ]; then echo "OK"; else echo "ERROR: $output"; fi
test_mongo:
stage: test
services:
- name: mongo:6.0.5-jammy
alias: mongostandalone
variables:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: dummy
before_script:
- apt-get update
- apt-get install -y curl
- apt-get install -y gnupg
- curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | tee /etc/apt/trusted.gpg.d/mongodb.asc > /dev/null
- echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
- apt-get update
- apt-get install -y mongodb-mongosh
script: |
output=$(mongosh --host mongostandalone:27017 --username root --password dummy --eval "db.stats()" 2>&1); if [ $? -eq 0 ]; then echo "OK"; else echo "ERROR: $output"; fi
UPDATE 3: 30 May
To simplify things, I've created this other repository to test my MongoDB Replica Set image
https://gitlab.com/sunnyatticsoftware/sandbox/mongo-rs-tester-dotnet
Basically it's a tiny .NET app with an automated test that can run with a
dotnet test
I have added a docker-compose.yml
just with a container for my replica set MongoDB image
version: '3.8'
services:
mongors:
image: registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
container_name: mongors
environment:
HOST: mongors
DELAY_INITIATE: 40
ports:
- 27017:27017
- 27018:27018
- 27019:27019
and the equivalent for .gitlab-ci.yml
which FAILS (and I don't know why)
image: mcr.microsoft.com/dotnet/sdk:7.0
stages:
- test
test:
stage: test
services:
- name: registry.gitlab.com/sunnyatticsoftware/sandbox/mongo-rs
alias: mongors
variables:
HOST: "mongors"
DELAY_INITIATE: "40"
before_script:
- sleep 30
script:
- dotnet test
allow_failure: false
The steps would be:
Requirements
- .NET 7 SDK or runtime
- Docker and docker-compose
How to run in local
- Clone the repo
- Add to
/etc/hosts
(or in WindowsC:\Windows\System32\drivers\etc\hosts
) this entry with the alias for localhost
127.0.0.1 mongors
- Execute
docker-compose up
to spin up amongors
container - Run tests with
dotnet test
Verify that the test is successful and it connects to the Mongo Replica Set database
Notice the connection string is mongodb://mongors:27017,mongors:27018,mongors:27019/?replicaSet=rs0&readPreference=primary&ssl=false
, which uses the alias mongors.
How to run in GitLab CI/CD
Now let's try the equivalent in GitLab CI/CD
- Run the CI/CD pipeline, which will spin up a container service with the alias
mongors
and which will run the dotnet test command as a script - Verify that the test is successful if it properly connects to the Mongo Replica Set database.
If the test is unsuccessful and the pipeline fails, it's because the Mongo RS container and tests behave differently when running within GitLab CI/CD. Why?
It fails with
System.TimeoutException : A timeout occurred after 30000ms selecting a server using CompositeServerSelector{ Selectors = MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector, LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 }, OperationsCountServerSelector }. Client view of cluster state is { ClusterId : "1", ConnectionMode : "ReplicaSet", Type : "ReplicaSet", State : "Connected", Servers : [{ ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/mongors:27017" }", EndPoint: "Unspecified/mongors:27017", ReasonChanged: "Heartbeat", State: "Connected", ServerVersion: 6.0.0, TopologyVersion: { "processId" : ObjectId("6475c89d5538f6657eda5c74"), "counter" : NumberLong(0) }, Type: "ReplicaSetGhost", WireVersionRange: "[0, 17]", LastHeartbeatTimestamp: "2023-05-30T09:59:11.3576006Z", LastUpdateTimestamp: "2023-05-30T09:59:11.3576012Z" }, { ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/mongors:27018" }", EndPoint: "Unspecified/mongors:27018", ReasonChanged: "Heartbeat", State: "Connected", ServerVersion: 6.0.0, TopologyVersion: { "processId" : ObjectId("6475c89ea07e16061b09e4ec"), "counter" : NumberLong(0) }, Type: "ReplicaSetGhost", WireVersionRange: "[0, 17]", LastHeartbeatTimestamp: "2023-05-30T09:59:11.3575679Z", LastUpdateTimestamp: "2023-05-30T09:59:11.3576205Z" }, { ServerId: "{ ClusterId : 1, EndPoint : "Unspecified/mongors:27019" }", EndPoint: "Unspecified/mongors:27019", ReasonChanged: "Heartbeat", State: "Connected", ServerVersion: 6.0.0, TopologyVersion: { "processId" : ObjectId("6475c89e4cf3656cf898c899"), "counter" : NumberLong(0) }, Type: "ReplicaSetGhost", WireVersionRange: "[0, 17]", LastHeartbeatTimestamp: "2023-05-30T09:59:11.3496821Z", LastUpdateTimestamp: "2023-05-30T09:59:11.3496847Z" }] }
UPDATE 4: 31 May
I have disabled shared runners and created and registered my own Linux runner with docker
type.
With this runner, the tests went successfully.
https://gitlab.com/sunnyatticsoftware/sandbox/mongo-rs-tester-dotnet/-/jobs/4385389688
I hoped services and CI/CD were agnostic of the runner..
Is there any explanation on why it wouldn't work with shared runners?