Open23

go-zeroを学ぶ3

135yshr135yshr

Docker サブコマンド

Dockerfile を作成するためのコマンドとのこと。

goctl docker --help
Generate Dockerfile

Usage:
  goctl docker [flags]

Flags:
      --base string      The base image to build the docker image, default scratch (default "scratch")
      --branch string    The branch of the remote repo, it does work with --remote
      --exe string       The executable name in the built image
      --go string        The file that contains main function
  -h, --help             help for docker
      --home string      The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
      --port int         The port to expose, default none
      --remote string    The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
                         The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
      --tz string        The timezone of the container (default "Asia/Shanghai")
      --version string   The goctl builder golang image version
135yshr135yshr

Dockerfile 生成

ユーザー管理モジュールと注文管理モジュールの Dockerfile を作成する

ユーザー管理モジュール

mkdir deployments/docker/user
cd deployments/docker/user
goctl docker -go ../../../mall/user/rpc/user.go -tz Asia/Tokyo -port 8080
cd ../../../
deployments/docker/user/Dockerfile
FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0

RUN apk update --no-cache && apk add --no-cache tzdata

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/user mall/user/rpc/user.go


FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Tokyo /usr/share/zoneinfo/Asia/Tokyo
ENV TZ Asia/Tokyo

WORKDIR /app
COPY --from=builder /app/user /app/user

EXPOSE 8080

CMD ["./user"]

注文管理モジュール

mkdir deployments/docker/order
cd deployments/docker/order
goctl docker -go ../../../mall/user/api/order.go -tz Asia/Tokyo -port 8888
cd ../../../
deployments/docker/order/Dockerfile
FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0

RUN apk update --no-cache && apk add --no-cache tzdata

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/order mall/order/api/order.go


FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Tokyo /usr/share/zoneinfo/Asia/Tokyo
ENV TZ Asia/Tokyo

WORKDIR /app
COPY --from=builder /app/order /app/order

EXPOSE 8888

CMD ["./order"]
135yshr135yshr

docker-compose.yaml の変更

作成した Dockerfile を docker-compose.yaml に追加する

docker-compose.yaml
@@ -7,6 +7,18 @@ networks:
     driver: bridge

 services:
+  user:
+    build:
+      context: .
+      dockerfile: deployments/docker/user/Dockerfile
+    ports:
+      - 8080:8080
+  order:
+    build:
+      context: .
+      dockerfile: deployments/docker/order/Dockerfile
+    ports:
+      - 8888:8888
   etcd:
     image: bitnami/etcd:3.5
     environment:
135yshr135yshr

動作確認

curl -i -X GET http://localhost:8888/api/order/get/1
curl: (7) Failed to connect to localhost port 8888 after 4 ms: Couldn't connect to server
make: *** [test-get-order] Error 7

サーバーが起動できていないようだ。。。

135yshr135yshr

ログを確認

docker compose logs user
docker-compose-user-1  | 2023/06/04 00:18:13 error: config file etc/user.yaml, open etc/user.yaml: no such file or directory
docker compose logs order
docker-compose-order-1  | 2023/06/04 00:18:13 error: config file etc/order.yaml, open etc/order.yaml: no such file or directory

設定ファイルが読み込めていなかった

135yshr135yshr

Dockerfile修正

設定ファイルをコピーする処理を追加する

deployments/docker/user/Dockerfile
@@ -13,6 +13,7 @@ ADD go.sum .
 RUN go mod download
 COPY . .
 RUN go build -ldflags="-s -w" -o /app/user mall/user/rpc/user.go
+RUN cp ./mall/user/rpc/etc/user.yaml /app/user.yaml


 FROM scratch
@@ -23,7 +24,8 @@ ENV TZ Asia/Tokyo

 WORKDIR /app
 COPY --from=builder /app/user /app/user
+COPY --from=builder /app/user.yaml /app/user.yaml

 EXPOSE 8080

-CMD ["./user"]
+CMD ["./user", "-f", "user.yaml"]
deployments/docker/order/Dockerfile
@@ -13,6 +13,7 @@ ADD go.sum .
 RUN go mod download
 COPY . .
 RUN go build -ldflags="-s -w" -o /app/order mall/order/api/order.go
+RUN cp ./mall/order/api/etc/order.yaml /app/order.yaml


 FROM scratch
@@ -23,7 +24,8 @@ ENV TZ Asia/Tokyo

 WORKDIR /app
 COPY --from=builder /app/order /app/order
+COPY --from=builder /app/order.yaml /app/order.yaml

 EXPOSE 8888

-CMD ["./order"]
+CMD ["./order", "-f", "order.yaml"]
135yshr135yshr

再起動

設定ファイルを読み込みできるように変更したので、改めて動作確認

docker compose up -d

起動確認

docker compose ps
NAME                     IMAGE               COMMAND                  SERVICE             CREATED             STATUS              PORTS
docker-compose-db-1      mysql:8.0           "docker-entrypoint.s…"   db                  31 seconds ago      Up 30 seconds       33060/tcp, 0.0.0.0:13306->3306/tcp
docker-compose-etcd-1    bitnami/etcd:3.5    "/opt/bitnami/script…"   etcd                31 seconds ago      Up 30 seconds       0.0.0.0:12379->2379/tcp, 0.0.0.0:12380->2380/tcp
docker-compose-redis-1   redis:7.0           "docker-entrypoint.s…"   redis               31 seconds ago      Up 30 seconds       0.0.0.0:16379->6379/tcp

起動失敗。。。

135yshr135yshr

再調査

ユーザー管理と注文管理が動作していないので再調査

docker compose logs user
docker-compose-user-1  | Starting rpc server at 0.0.0.0:8080...
docker-compose-user-1  | {"level":"warn","ts":"2023-06-04T01:57:43.927538+0900","logger":"etcd-client","caller":"v3@v3.5.8/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0x40005f6000/127.0.0.1:12379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:12379: connect: connection refused\""}
docker-compose-user-1  | {"@timestamp":"2023-06-04T01:57:43.927+09:00","caller":"zrpc/server.go:92","content":"context deadline exceeded","level":"error"}
docker-compose-user-1  | panic: context deadline exceeded
docker-compose-user-1  |
docker-compose-user-1  | goroutine 1 [running]:
docker-compose-user-1  | github.com/zeromicro/go-zero/zrpc.(*RpcServer).Start(0x1c84d58?)
docker-compose-user-1  | 	/go/pkg/mod/github.com/zeromicro/go-zero@v1.5.2/zrpc/server.go:93 +0x80
docker-compose-user-1  | main.main()
docker-compose-user-1  | 	/build/mall/user/rpc/user.go:38 +0x1dc
docker compose logs order
docker-compose-order-1  | {"level":"warn","ts":"2023-06-04T01:57:43.740235+0900","logger":"etcd-client","caller":"v3@v3.5.8/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0x400023c1c0/127.0.0.1:12379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:12379: connect: connection refused\""}
docker-compose-order-1  | 2023/06/04 01:57:43 rpc dial: etcd://127.0.0.1:12379/user.rpc, error: context deadline exceeded, make sure rpc service "user.rpc" is already started

docker compose 内でサーバー間の通信ができないんだった

135yshr135yshr

再修正

サーバー間で接続できるようにプログラムの設定ファイルを変更する

mall/order/api/etc/order.yaml
@@ -4,5 +4,5 @@ Port: 8888
 UserRpc:
   Etcd:
     Hosts:
-    - 127.0.0.1:12379
+    - etcd:2379
     Key: user.rpc
mall/user/rpc/etc/user.yaml
index 1218100..6f6a300 100644
--- a/mall/user/rpc/etc/user.yaml
+++ b/mall/user/rpc/etc/user.yaml
@@ -2,10 +2,10 @@ Name: user.rpc
 ListenOn: 0.0.0.0:8080
 Etcd:
   Hosts:
-  - 127.0.0.1:12379
+  - etcd:2379
   Key: user.rpc
-DataSource: root:password@tcp(127.0.0.1:13306)/mall?parseTime=true
+DataSource: root:password@tcp(db:3306)/mall?parseTime=true
 Table: user
 Cache:
-  - Host: 127.0.0.1:16379
+  - Host: redis:6379

docker compose で、bridge ネットワーク使うとDNS引けなくなるので、docker-compose.yaml も修正

deployments/docker-compose/docker-compose.yaml
@@ -1,11 +1,5 @@
 version: '3'

-networks:
-  etcd-network:
-    driver: bridge
-  db-network:
-    driver: bridge
-
 services:
   user:
     build:
@@ -27,8 +21,6 @@ services:
     ports:
       - "12379:2379"
       - "12380:2380"
-    networks:
-      - etcd-network

   db:
     image: mysql:8.0
@@ -43,8 +35,6 @@ services:
     restart: always
     ports:
       - "13306:3306"
-    networks:
-      - db-network

   redis:
     image: redis:7.0
135yshr135yshr

再々起動

docker compose up -d
docker compose ps
NAME                     IMAGE                  COMMAND                  SERVICE             CREATED             STATUS              PORTS
docker-compose-db-1      mysql:8.0              "docker-entrypoint.s…"   db                  4 minutes ago       Up 4 minutes        33060/tcp, 0.0.0.0:13306->3306/tcp
docker-compose-etcd-1    bitnami/etcd:3.5       "/opt/bitnami/script…"   etcd                4 minutes ago       Up 4 minutes        0.0.0.0:12379->2379/tcp, 0.0.0.0:12380->2380/tcp
docker-compose-order-1   docker-compose-order   "./order -f order.ya…"   order               4 minutes ago       Up 4 minutes        0.0.0.0:8888->8888/tcp
docker-compose-redis-1   redis:7.0              "docker-entrypoint.s…"   redis               4 minutes ago       Up 4 minutes        0.0.0.0:16379->6379/tcp
docker-compose-user-1    docker-compose-user    "./user -f user.yaml"    user                4 minutes ago       Up 4 minutes        0.0.0.0:8080->8080/tcp
135yshr135yshr

動作確認

接続成功

curl -i -X GET http://localhost:8888/api/order/get/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-f1e4a4cd0811641b4c48c0fb170cafc4-f07db5d7da0dfd15-00
Date: Sat, 03 Jun 2023 17:15:45 GMT
Content-Length: 30

{"id":"1","name":"test order"}%
135yshr135yshr

接続できるようになったけど、動作確認するために都度コンパイルが必要なのは手間だな

135yshr135yshr

環境変数とか使って、docker compose からでもローカル環境からでも接続できるように修正する

135yshr135yshr

環境変数対応

go-zero の設定ファイルに環境変数が使えることがわかったので、環境変数から接続情報を注入できるように書き換える

該当箇所は、ここ

deployments/docker-compose/docker-compose.yaml
@@ -1,34 +1,48 @@
 version: '3'

-networks:
-  etcd-network:
-    driver: bridge
-  db-network:
-    driver: bridge
-
 services:
   user:
     build:
       context: ../../
       dockerfile: deployments/docker/user/Dockerfile
+    environment:
+      - CACHE_HOST=redis
+      - CACHE_PORT=6379
+      - DATABASE_NAME=${DATABASE_NAME:-mall}
+      - DATABASE_USER=${DATABASE_USER:-user}
+      - DATABASE_PASS=${DATABASE_PASS:-password}
+      - DATABASE_HOST=db
+      - DATABASE_PORT=3306
+      - ETCD_HOST=etcd
+      - ETCD_PORT=2379
     ports:
       - 8080:8080
+    depends_on:
+      - etcd
+      - db
+      - redis
+
   order:
     build:
       context: ../../
       dockerfile: deployments/docker/order/Dockerfile
+    environment:
+      - ETCD_HOST=etcd
+      - ETCD_PORT=2379
     ports:
       - 8888:8888
+    depends_on:
+      - etcd
+      - user
+
   etcd:
     image: bitnami/etcd:3.5
     environment:
       - ALLOW_NONE_AUTHENTICATION=yes
       - ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379
     ports:
-      - "12379:2379"
-      - "12380:2380"
-    networks:
-      - etcd-network
+      - ${ETCD_PORT_1:-12379}:2379
+      - ${ETCD_PORT_2:-12380}:2380

   db:
     image: mysql:8.0
@@ -36,21 +50,19 @@ services:
       - ../docker/mysql/data:/var/lib/mysql
       - ../docker/mysql/conf.d:/etc/mysql/conf.d
     environment:
-      MYSQL_DATABASE: mall
-      MYSQL_ROOT_PASSWORD: password
-      MYSQL_USER: user
-      MYSQL_PASSWOR: password
+      MYSQL_DATABASE: ${DATABASE_NAME:-mall}
+      MYSQL_USER: ${DATABASE_USER:-user}
+      MYSQL_PASSWORD: ${DATABASE_PASS:-password}
+      MYSQL_ROOT_PASSWORD: ${DATABASE_PASS:-password}
     restart: always
     ports:
-      - "13306:3306"
-    networks:
-      - db-network
+      - ${DATABASE_PORT:-13306}:3306

   redis:
     image: redis:7.0
     volumes:
       - ../docker/redis/data:/data
     ports:
-      - "16379:6379"
+      - ${REDIS_PORT:-16379}:6379
135yshr135yshr
mall/order/api/etc/order.yaml
@@ -4,5 +4,5 @@ Port: 8888
 UserRpc:
   Etcd:
     Hosts:
-    - 127.0.0.1:12379
+    - $ETCD_HOST:$ETCD_PORT
     Key: user.rpc
135yshr135yshr
mall/order/api/order.go
@@ -18,7 +18,7 @@ func main() {
        flag.Parse()

        var c config.Config
-       conf.MustLoad(*configFile, &c)
+       conf.MustLoad(*configFile, &c, conf.UseEnv())

        server := rest.MustNewServer(c.RestConf)
        defer server.Stop()
135yshr135yshr
mall/user/rpc/etc/user.yaml
@@ -2,10 +2,10 @@ Name: user.rpc
 ListenOn: 0.0.0.0:8080
 Etcd:
   Hosts:
-  - 127.0.0.1:12379
+  - $ETCD_HOST:$ETCD_PORT
   Key: user.rpc
-DataSource: root:password@tcp(127.0.0.1:13306)/mall?parseTime=true
+DataSource: $DATABASE_USER:$DATABASE_PASS@tcp($DATABASE_HOST:$DATABASE_PORT)/$DATABASE_NAME?parseTime=true
 Table: user
 Cache:
-  - Host: 127.0.0.1:16379
+  - Host: $CACHE_HOST:$CACHE_PORT
135yshr135yshr
mall/user/rpc/user.go
@@ -22,7 +22,7 @@ func main() {
        flag.Parse()

        var c config.Config
-       conf.MustLoad(*configFile, &c)
+       conf.MustLoad(*configFile, &c, conf.UseEnv())
        ctx := svc.NewServiceContext(c)

        s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
135yshr135yshr

conf.UseEnv() のこと書いてなかった

conf.UseEnv() とは

conf.MustLoad メソッド実行時にオプションに指定することで、読み込む設定ファイル内で環境変数が使えるようになる。
環境ファイルの置き換えには、os.ExpandEnvメソッド を使っている。 ${var}$var を展開する。