迫使go docker容器等待MySQL容器的到来(附实例)

362 阅读4分钟

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

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

结构

.
├── cmd
│   └── app
│       └── main.go
├── docker
│   └── dev
│       ├── docker-compose.yaml
│       └── go
│           ├── Dockerfile
│           └── init.sh
├── go.mod
└── go.sum

文件

main.go

package main

import (
	"database/sql"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	log.Println("Staring the app ...")

	db, err := sql.Open("mysql", "user:pass@tcp(app-db:3306)/db")
	if err != nil {
		log.Fatalln(err)
	}
	defer db.Close()

	if err := db.Ping(); err != nil {
		log.Fatalln(err)
	}

	log.Println("Connected to the db ...")

	select {}
}

Docker文件

#
# STAGE 1: prepare
#
FROM golang:1.13.1-alpine3.10 as prepare

WORKDIR /source

COPY go.mod .
COPY go.sum .

RUN go mod download

#
# STAGE 2: build
#
FROM prepare AS build

COPY . .

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

#
# STAGE 3: run
#
FROM alpine:3.10 as run

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

RUN chmod +x /init.sh

ENTRYPOINT ["/init.sh"]

init.sh

你可以对其他服务使用完全相同的脚本,或者直接在这里添加其他服务。

#!/bin/sh

set -eu

echo "Checking DB connection ..."

i=0
until [ $i -ge 10 ]
do
  nc -z app-db 3306 && break

  i=$(( i + 1 ))

  echo "$i: Waiting for DB 1 second ..."
  sleep 1
done

if [ $i -eq 10 ]
then
  echo "DB connection refused, terminating ..."
  exit 1
fi

echo "DB is up ..."

/app

docker-compose.yaml

version: "3.4"

services:

  app-go:
    build:
      context: "../.."
      dockerfile: "docker/dev/go/Dockerfile"
    ports:
      - "8001:8000"

  app-db:
    image: "mysql:5.7.24"
    ports:
      - "3001:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "root"
      MYSQL_DATABASE: "db"

测试

10秒内连接失败

实际上我在脚本中把1 秒改为0.001 (1ms),以便在这里模拟终止。

Recreating dev_app-go_1 ... done
Starting dev_app-db_1   ... done
Attaching to dev_app-db_1, dev_app-go_1
app-db_1  | 2020-04-17T19:31:26.156962Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated.
app-db_1  | 2020-04-17T19:31:26.158227Z 0 [Note] mysqld (mysqld 5.7.24) starting as process 1 ...
app-db_1  | 2020-04-17T19:31:26.161203Z 0 [Note] InnoDB: PUNCH HOLE support available
app-db_1  | 2020-04-17T19:31:26.161454Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
app-go_1  | Checking DB connection ...
app-go_1  | 1: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.161563Z 0 [Note] InnoDB: Uses event mutexes
app-go_1  | 2: Waiting for DB 1 second ...
app-go_1  | 3: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.161853Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used
app-go_1  | 4: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.162130Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.11
app-db_1  | 2020-04-17T19:31:26.162368Z 0 [Note] InnoDB: Using Linux native AIO
app-go_1  | 5: Waiting for DB 1 second ...
app-go_1  | 6: Waiting for DB 1 second ...
app-go_1  | 7: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.162986Z 0 [Note] InnoDB: Number of pools: 1
app-go_1  | 8: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.163323Z 0 [Note] InnoDB: Using CPU crc32 instructions
app-go_1  | 9: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.164968Z 0 [Note] InnoDB: Initializing buffer pool, total size = 128M
app-db_1  | 2020-04-17T19:31:26.172335Z 0 [Note] InnoDB: Completed initialization of buffer pool
app-go_1  | 10: Waiting for DB 1 second ...
app-go_1  | DB connection refused, terminating ...
dev_app-go_1 exited with code 1

3秒内成功连接

Recreating dev_app-go_1 ... done
Starting dev_app-db_1   ... done
Attaching to dev_app-db_1, dev_app-go_1
app-db_1  | 2020-04-17T19:31:26.156962Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated.
app-db_1  | 2020-04-17T19:31:26.158227Z 0 [Note] mysqld (mysqld 5.7.24) starting as process 1 ...
app-db_1  | 2020-04-17T19:31:26.161203Z 0 [Note] InnoDB: PUNCH HOLE support available
app-db_1  | 2020-04-17T19:31:26.161454Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
app-go_1  | Checking DB connection ...
app-go_1  | 1: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:31:26.161563Z 0 [Note] InnoDB: Uses event mutexes
app-go_1  | 2: Waiting for DB 1 second ...
app-go_1  | 3: Waiting for DB 1 second ...
app-db_1  | 2020-04-17T19:45:21.324028Z 0 [Note] mysqld: ready for connections.
app-db_1  | Version: '5.7.24'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
app-go_1  | DB is up ...
app-db_1  | 2020-04-17T19:45:21.663343Z 2 [Note] Got an error reading communication packets
app-go_1  | 2020/04/17 19:45:21 Staring the app ...
app-go_1  | 2020/04/17 19:45:21 Connected to the db ...