强制Go Docker容器等待Cassandra容器的实例

284 阅读3分钟

假设你有两个docker服务/容器。一个必须准备好接受来自另一个的连接。正如你刚才注意到的,我没有说 "一个必须运行",因为 "准备接受连接 "和 "运行 "之间是有区别的。docker compose的depends_on 关键并不总是能解决问题,因为它只是检查容器是否正在运行。如果一个服务依赖于Cassandra、MySQL、RabbitMQ、Elasticsearch等服务,往往会出现这种情况。它们需要时间来接受连接。为了解决这个问题,我们可以使用一个额外的脚本。这个脚本将定期ping这些服务,直到它们准备好接受连接,然后再实际运行主服务。

在这个例子中,我们有一个Go和Cassandra服务。Go依赖于Cassandra。Cassandra接受连接的速度很慢。我们将在Go中使用我们的脚本,强迫它首先等待Cassandra接受连接。一旦Cassandra说它准备好了,我们就会启动Go服务。

结构

├── cassandra
│   └── cassandra.go
├── docker
│   ├── cassandra
│   │   ├── Dockerfile
│   │   ├── init.sh
│   │   └── keyspace.cql
│   ├── docker-compose.yaml
│   └── go
│       ├── Dockerfile
│       └── init.sh
└── main.go

文件

cassandra.go

package cassandra

import (
	"time"

	"github.com/gocql/gocql"
)

// The `gocql: no response received from cassandra within timeout period` error
// will be prevented by increasing the default timeout value. e.g. 5 sec
type Config struct {
	Hosts        []string
	Port         int
	ProtoVersion int
	Consistency  string
	Keyspace     string
	Timeout      time.Duration
}

func New(config Config) (*gocql.Session, error) {
	cluster := gocql.NewCluster(config.Hosts...)

	cluster.Port = config.Port
	cluster.ProtoVersion = config.ProtoVersion
	cluster.Keyspace = config.Keyspace
	cluster.Consistency = gocql.ParseConsistency(config.Consistency)
	cluster.Timeout = config.Timeout

	return cluster.CreateSession()
}

main.go

package main

import (
	"log"
	"time"

	"github.com/you/auth/cassandra"
)

func main() {
	cass, err := cassandra.New(cassandra.Config{
		// From docker go to docker cassandra: "172.17.0.1"
		// From local go to docker cassandra: "127.0.0.1"
		Hosts:        []string{"172.17.0.1"},
		Port:         9042,
		ProtoVersion: 4,
		Consistency:  "Quorum",
		Keyspace:     "tetris",
		Timeout:      time.Second * 5,
	})
	if err != nil {
		log.Fatalln(err)
	}
	defer cass.Close()

	log.Printf("%+v\n", cass)
}

docker-compose.yaml

version: "3.7"

services:

  tetris-cassandra:
    build: "cassandra"
    container_name: "tetris-cassandra"
    ports:
      - "9042:9042"
    environment:
      - "MAX_HEAP_SIZE=256M"
      - "HEAP_NEWSIZE=128M"

  tetris-go:
    build:
      context: ".."
      dockerfile: "docker/go/Dockerfile"
    container_name: "tetris-go"
    environment:
      DB_HOST: "tetris-cassandra"
      DB_PORT: "9042"

Dockerfile (cassandra)

FROM cassandra:3.11.9

COPY ./keyspace.cql /keyspace.cql
COPY ./init.sh /init.sh

RUN chmod +x /init.sh

ENTRYPOINT ["./init.sh"]

init.sh (cassandra)

#!/bin/bash

set -eu

echo "Creating keyspace ..."

sleep=1
index=0
limit=60

while :
do
  cqlsh < /keyspace.cql && echo "$(date) Created keyspace ..." && break

  index=$(( index + 1 ))

  echo "$(date) Waiting for keyspace $sleep seconds ($index/$limit) ..."
  sleep $sleep

  if [ $index -eq $limit ]
  then
    echo "$(date) Failed to create keyspace, terminating ..."
    exit 1
  fi
done &

./docker-entrypoint.sh "$@"

keyspace.cql (cassandra)

DROP KEYSPACE IF EXISTS tetris;

CREATE KEYSPACE IF NOT EXISTS tetris
WITH replication = {
    'class': 'NetworkTopologyStrategy',
    'datacenter1': 1
};

Dockerfile (go)

FROM golang:1.15-alpine3.12 as build

WORKDIR /source
COPY . .

RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/tetris main.go

FROM alpine:3.12

COPY --from=build /source/bin/tetris /bin/tetris
COPY --from=build /source/docker/go/init.sh /init.sh

RUN chmod +x /init.sh

ENTRYPOINT ["./init.sh"]

init.sh (go)

#!/bin/sh

set -eu

echo "$(date) Connecting to database ..."

sleep=1
index=0
limit=60

until [ $index -ge $limit ]
do
  nc -z "${DB_HOST}" "${DB_PORT}" && break

  index=$(( index + 1 ))

  echo "$(date) Waiting for database $sleep seconds ($index/$limit) ..."
  sleep $sleep
done

if [ $index -eq $limit ]
then
  echo "$(date) Failed to connect to database, terminating ..."
  exit 1
fi

echo "$(date) Connected to database ..."
echo "$(date) Allowing database 3 seconds to complete migrations ..."
sleep 3

./bin/tetris

测试

$ docker-compose -f docker/docker-compose.yaml up

tetris-cassandra    | Mon 01 Feb 2021 10:02:52 PM UTC Waiting for keyspace 1 seconds (14/60) ...
tetris-go           | Mon Feb  1 22:02:52 UTC 2021 Waiting for database 1 seconds (19/60) ...
tetris-go           | Mon Feb  1 22:02:53 UTC 2021 Waiting for database 1 seconds (20/60) ...
tetris-cassandra    | Connection error: ('Unable to connect to any servers', {'127.0.0.1': error(111, "Tried connecting to [('127.0.0.1', 9042)]. Last error: Connection refused")})
tetris-cassandra    | Mon 01 Feb 2021 10:02:53 PM UTC Waiting for keyspace 1 seconds (15/60) ...
tetris-go           | Mon Feb  1 22:02:54 UTC 2021 Waiting for database 1 seconds (21/60) ...
tetris-cassandra    | INFO  [main] 2021-02-01 22:02:54,643 CassandraDaemon.java:650 - Startup complete
tetris-go           | Mon Feb  1 22:02:55 UTC 2021 Connected to database ...
tetris-go           | Mon Feb  1 22:02:55 UTC 2021 Allowing database 3 seconds to complete migrations ...
tetris-cassandra    | INFO  2021-02-01 22:02:55,354 MigrationManager.java:338 - Create new Keyspace: KeyspaceMetadata{name=tetris, replication=ReplicationParams{class=NetworkTopologyStrategy, datacenter1=1}}}
tetris-cassandra    | Mon 01 Feb 2021 10:02:55 PM UTC Created keyspace ...
tetris-cassandra    | INFO  2021-02-01 22:02:55,940 CassandraRoleManager.java:372 - Created default superuser role 'cassandra'
tetris-go           | 2021/02/01 22:02:58 &{cons:4 pageSize:5000 ... cfg:{Hosts:[172.17.0.1] CQLVersion:3.0.0 ProtoVersion:4 Port:9042 Keyspace:tetris Consistency:4 ... isClosed:false}