在本地机器上通过Docker容器启动带有DB连接的Golang GraphQL服务器
请用中文将以下内容转述:
题目
就像标题所说的一样。在上次之前,我们在前端和后端分别实施了GraphQL。
虽然还有很多要实现的地方,但是我打算将这个应用程序部署到GKE上,虽然目前只在本地机器上运行着。所以首先尝试将应用程序进行Docker化。
这次只涉及后端(使用Golang)。连接的数据库仍然是本地的Docker容器。
相关文章索引
-
- 第12回「GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)」
-
- 第11回「Dataloadersを使ったN+1問題への対応」
-
- 第10回「GraphQL(gqlgen)エラーハンドリング」
-
- 第9回「GraphQLにおける認証認可事例(Auth0 RBAC仕立て)」
-
- 第8回「GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例」
-
- 第7回「GraphQLにおけるRelayスタイルによるページング実装(後編:フロントエンド)」
-
- 第6回「GraphQLにおけるRelayスタイルによるページング実装(前編:バックエンド)」
-
- 第5回「DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動」
-
- 第4回「graphql-codegenでフロントエンドをGraphQLスキーマファースト」
-
- 第3回「go+gqlgenでGraphQLサーバを作る(GORM使ってDB接続)」
-
- 第2回「NuxtJS(with Apollo)のTypeScript対応」
- 第1回「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」
开发环境
操作系统 – Linux(Ubuntu)
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
# 后台
言语 – 前进
$ go version
go version go1.13.3 linux/amd64
包管理器 – Go Modules
GoLand – 集成开发环境
GoLand 2019.3.1
Build #GO-193.5662.65, built on December 23, 2019
# 用于容器化的Docker容器。
Docker:
$ $ sudo docker -v
Docker version 19.03.5, build 633a0ea838
用docker-compose
$ docker-compose -v
docker-compose version 1.23.1, build b02f1306
实践
这次的全部源代码在下面的链接上。
https://github.com/sky0621/study-graphql/tree/v0.5.0
项目构成
$ pwd
/home/sky0621/src/github.com/sky0621/study-graphql
$
$ tree -L 1
.
├── backend
├── docker-compose.yml
├── Dockerfile
├── frontend
├── persistence
├── README.md
└── schema
本次文章涉及的是Golang源代码中的”backend”文件夹下的源代码和”Dockerfile”文件,以及在MySQL容器启动时,为应用程序创建必需的表格的”persistence”文件夹下的DDL文件。
Dockerfile的中文释义是什么?
首先是Dockerfile。通过多阶段构建,在实际运行中使用超轻量的scratch基础镜像。以下是部分重用的内容。
# step 1: build go app
FROM golang:1.13.5-alpine3.11 as build-step
# for go mod download
RUN apk add --update --no-cache ca-certificates git
RUN mkdir /go-app
WORKDIR /go-app
COPY backend/go.mod .
COPY backend/go.sum .
RUN go mod download
COPY backend .
RUN cd server && CGO_ENABLED=0 go build -o /go/bin/go-app
# -----------------------------------------------------------------------------
# step 2: exec
FROM scratch
COPY --from=build-step /go/bin/go-app /go/bin/go-app
EXPOSE 5050
ENTRYPOINT ["/go/bin/go-app"]
由于在文件”backend/server/main.go”中存在main函数,所以这个文件是构建目标。因此,在”RUN cd server”后面继续执行”go build”命令。此外,Go应用程序作为GraphQL服务器实现,并在端口5050上启动,因此写入”EXPOSE 5050″。然而,根据下文所述,仅凭此行不足以使主机能够访问容器。
参考链接:http://docs.docker.jp/engine/reference/builder.html#expose
docker-compose.yml 在中国大陆的母语中的释义:Docker组合文件。
接下来,当在本地控制各种Docker容器的时候,Docker Compose便成为一个很方便的工具。
version: '3'
services:
db:
restart: always
image: mysql:5.7.24
command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_USER: localuser
MYSQL_PASSWORD: localpass
MYSQL_DATABASE: localdb
volumes:
- ./persistence/init:/docker-entrypoint-initdb.d
networks:
- study-graphql-network
app:
build: .
ports:
- "80:5050"
networks:
- study-graphql-network
volumes:
localdb:
external: false
networks:
study-graphql-network:
external: true
数据库服务 kuò fú wù)
第一个服务定义中的“db”指的是就像它看起来的那样,用于启动MySQL容器的内容。这个没啥特别要提及的。通过定义的信息,数据库就可以创建了。
顺便一提,有准备好的DDL,当容器启动时会执行这个DDL来创建表。
$ tree persistence/
persistence/
├── init
│ └── 1_create.sql
└── README.md
$
$ cat persistence/init/1_create.sql
CREATE TABLE IF NOT EXISTS `todo` (
`id` varchar(64) NOT NULL,
`text` varchar(256) NOT NULL,
`done` bool NOT NULL,
`user_id` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;
CREATE TABLE IF NOT EXISTS `user` (
`id` varchar(64) NOT NULL,
`name` varchar(256) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin;
应用程序服务
第二个服务定义中的”app”将使用位于同一层级的Dockerfile来构建Go应用程序,并通过以下指定使得在主机的80端口访问容器内的应用程序成为可能。
ports:
- "80:5050"
网络
由于这次是在Go应用程序中实现对MySQL数据库表的访问,所以如果不能在容器之间进行通信,就没有意义。
在Docker中,可以明确地创建网络,以实现容器在同一网络内的通信,因此进行了这样的指定。
首先,建立网络。
$ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
1abb47a7ebd9 bridge bridge local
0843ed48f717 host host local
09614bd65e4d none null local
$
$ sudo docker network create study-graphql-network
如果这样的话,
$ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
1abb47a7ebd9 bridge bridge local
0843ed48f717 host host local
09614bd65e4d none null local
ba8a943cb12b study-graphql-network bridge local
新的网络就是这样创建的,然后只需在docker-compose.yml文件中明确指定使用即可。
services:
db:
〜〜:
networks:
- study-graphql-network
app:
〜〜:
networks:
- study-graphql-network
networks:
study-graphql-network:
external: true
Go语言的main函数
Go应用程序作为GraphQL服务器启动,并根据在启动时指定的数据源连接字符串连接到数据库。
在下面的部分,指定的主机IP实际上是经过仔细查询的。
数据源 = “localuser:localpass@tcp(172.19.0.1:3306)/ localdb?”
在之前创建的Docker网络中,如果按照以下方式显示其信息,那么该网络的网关IP将会显示在其中,将其进行记录。
$ sudo docker network inspect study-graphql-network
[
{
"Name": "study-graphql-network",
"Id": "ba8a943cb12b07e6dcddbc421a5d5b63c8f48cf526bf1da3ec454bad26233fad",
"Created": "2020-01-07T08:54:36.947913642+09:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
下面是包含主函数的源代码。虽然只有这个文件的话没有意义,但还是附上了。
package main
import (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/handler"
"github.com/jinzhu/gorm"
"github.com/sky0621/study-graphql/backend"
_ "github.com/go-sql-driver/mysql"
)
const dataSource = "localuser:localpass@tcp(172.19.0.1:3306)/localdb?charset=utf8&parseTime=True&loc=Local"
const defaultPort = "5050"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
db, err := gorm.Open("mysql", dataSource)
if err != nil {
panic(err)
}
if db == nil {
panic(err)
}
defer func() {
if db != nil {
if err := db.Close(); err != nil {
panic(err)
}
}
}()
db.LogMode(true)
http.Handle("/", handler.Playground("GraphQL playground", "/query"))
http.Handle("/query", handler.GraphQL(backend.NewExecutableSchema(backend.Config{Resolvers: &backend.Resolver{DB: db}})))
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
建造
既然已经在使用docker-compose,那就执行docker-compose build命令吧。
初次执行可能需要一定时间。(之后的执行由于有缓存效果,速度会很快。)
$ sudo docker-compose build
db uses an image, skipping
Building app
Step 1/13 : FROM golang:1.13.5-alpine3.11 as build-step
1.13.5-alpine3.11: Pulling from library/golang
e6b0cf9c0882: Pull complete
2848faf0eed1: Pull complete
〜〜省略〜〜
Step 13/13 : ENTRYPOINT ["/go/bin/go-app"]
---> Running in ab52efb17aa9
Removing intermediate container ab52efb17aa9
---> 10ab8cd2ef9e
Successfully built 10ab8cd2ef9e
Successfully tagged study-graphql_app:latest
启动Docker
$ sudo docker-compose up
Starting study-graphql_db_1_7a805d40cf64 ... done
Starting study-graphql_app_1_3cd84c32df8a ... done
Attaching to study-graphql_db_1_7a805d40cf64, study-graphql_app_1_3cd84c32df8a
db_1_7a805d40cf64 | 2020-01-09T16:10:28.082404Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1_7a805d40cf64 | 2020-01-09T16:10:28.084061Z 0 [Note] mysqld (mysqld 5.7.24) starting as process 1 ...
app_1_3cd84c32df8a | 2020/01/09 16:10:28 connect to http://localhost:5050/ for GraphQL playground
db_1_7a805d40cf64 | 2020-01-09T16:10:28.088730Z 0 [Note] InnoDB: PUNCH HOLE support available
db_1_7a805d40cf64 | 2020-01-09T16:10:28.088753Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
〜〜〜 省略 〜〜〜
db_1_7a805d40cf64 | 2020-01-09T16:10:28.342594Z 0 [Note] Event Scheduler: Loaded 0 events
db_1_7a805d40cf64 | 2020-01-09T16:10:28.345471Z 0 [Note] mysqld: ready for connections.
db_1_7a805d40cf64 | Version: '5.7.24' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
确认动作



总结
下一次的话,可能是将前端(Vue.js/Nuxt.js)进行Docker化。
索性考虑将其部署在GKE上吧。。。