はじめに
先日、研究室の後輩たちと技育CAMPハッカソンに参加してきました。
ハッカソンで何を開発したか等は、後輩が記事にまとめてくれたので、良ければ以下の記事をご覧ください。
この記事では、Go言語の経験者がほとんどいない中、バックエンドの開発言語にGoを採用した今回のハッカソンで、入門Gopherのためにも快適な開発ができるよう環境構築をしたので、その紹介をさせていただきます。
紹介するにあたって、Goでのサーバーサイド開発に使えるテンプレートリポジトリも作成しました。Goを使ってみたいけど環境構築はめんどくさい方や、ハッカソン等でパッと使えるGoのテンプレートリポジトリを探している方は、使ってみてください。
リポジトリの説明
GO + PostgreSQL での開発を、Docker と Air により快適なものとするためのテンプレートリポジトリです。
Go で開発する API サーバー用のコンテナと、PostgreSQL による DB 用のコンテナ、DB に対してGUIで確認・操作を行えるPGAdmin用のコンテナの3つを Docker Compose により立ち上げます。また、Air によるホットリロードのおかげで、Go のソースコードに対する変更は、立ち上げている API サーバー用のコンテナに反映されます。
以下が作成したリポジトリです。
手元で動かしてみよう!
1つ1つのファイルの説明よりも、動きを見た方がわかりやすいかと思いますので、さっそく動かし方を紹介します!
細かいファイルの説明などは後半で記載していますので、気になる方はご覧ください。
環境構築
- Dockerをインストール
- 本リポジトリをクローンするか、テンプレートとして選択してリポジトリを作成
docker network create api-db-network
docker network create db-pgadmin-network
docker compose build
Dockerのビルドに成功すると下記のような出力が得られます。
> docker compose build
2024/09/25 13:34:29 http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed
[+] Building 0.0s (0/0) docker:default
[+] Building 18.9s (10/10) FINISHED docker:default
=> [api internal] load build definition from go.dockerfile 0.0s
=> => transferring dockerfile: 239B 0.0s
=> [api internal] load metadata for docker.io/library/golang:1.23.1-alpine3.20 1.5s
=> [api internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [api internal] load build context 0.0s
=> => transferring context: 149B 0.0s
=> [api 1/5] FROM docker.io/library/golang:1.23.1-alpine3.20@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 5.7s
=> => resolve docker.io/library/golang:1.23.1-alpine3.20@sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 0.0s
=> => sha256:ac67716dd016429be8d4c2c53a248d7bcdf06d34127d3dc451bda6aa5a87bc06 10.29kB / 10.29kB 0.0s
=> => sha256:3058c543b93017c20123f4ffe8ca779c88ade0102361ab9a290bf7d590360fc4 1.92kB / 1.92kB 0.0s
=> => sha256:b5ada884192f173018fcb39688bd70545669ab105941231067ea5dbed4ac6914 2.07kB / 2.07kB 0.0s
=> => sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 3.62MB / 3.62MB 0.6s
=> => sha256:ab19dfae90efdd651c37b568f750d129c6d7c47d3a902982eefcd7c6567ccd5d 290.88kB / 290.88kB 0.3s
=> => sha256:e7bff916ab0c126c9d943f0c481a905f402e00f206a89248f257ef90beaabbd8 74.00MB / 74.00MB 2.9s
=> => sha256:78cee99375e3e0bb16a7bcb00218932b90225708c1a53e97b98b5230fe87b86a 125B / 125B 0.7s
=> => extracting sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 0.1s
=> => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B 0.8s
=> => extracting sha256:ab19dfae90efdd651c37b568f750d129c6d7c47d3a902982eefcd7c6567ccd5d 0.1s
=> => extracting sha256:e7bff916ab0c126c9d943f0c481a905f402e00f206a89248f257ef90beaabbd8 2.6s
=> => extracting sha256:78cee99375e3e0bb16a7bcb00218932b90225708c1a53e97b98b5230fe87b86a 0.0s
=> => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 0.0s
=> [api 2/5] COPY ./app /go/app/ 0.2s
=> [api 3/5] WORKDIR /go/app/ 0.0s
=> [api 4/5] RUN go mod download && go mod tidy 4.5s
=> [api 5/5] RUN go install github.com/air-verse/air@latest 6.4s
=> [api] exporting to image 0.5s
=> => exporting layers 0.5s
=> => writing image sha256:920413d3115f1a2d59f591588e3b0e7531a80f92a57f4f17f7553433e37e54e0 0.0s
=> => naming to docker.io/library/go-dev-template-api
実行方法
下記コマンドを実行し、Docker Composeを実行します。
docker compose up
実行時のログを確認すると、Airが起動していることや、テーブルの作成やテストデータの挿入、APIサーバーからデータベースの接続にも成功していることが確認できます!
> docker compose up
[+] Running 2/0
✔ Volume "go-dev-template_pgadmin-data" Created 0.0s
✔ Volume "go-dev-template_db" Created 0.0s
- Container db Created 0.1s
- Container api Created 0.0s
- Container pgadmin4 Created 0.0s
Attaching to api, db, pgadmin4
db | The files belonging to this database system will be owned by user "postgres".
db | This user must also own the server process.
db |
db | The database cluster will be initialized with locale "en_US.utf8".
db | The default database encoding has accordingly been set to "UTF8".
db | The default text search configuration will be set to "english".
db |
db | Data page checksums are disabled.
db |
db | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db | creating subdirectories ... ok
db | selecting dynamic shared memory implementation ... posix
db | selecting default max_connections ... 100
db | selecting default shared_buffers ... 128MB
db | selecting default time zone ... Etc/UTC
db | creating configuration files ... ok
db | running bootstrap script ... ok
api |
api | __ _ ___
api | / /\ | | | |_)
api | /_/--\ |_| |_| \_ v1.60.0, built with Go go1.23.1
api |
api | watching .
api | !exclude tmp
api | building...
db | performing post-bootstrap initialization ... ok
db | syncing data to disk ... ok
db |
db |
db | Success. You can now start the database server using:
db |
db | pg_ctl -D /var/lib/postgresql/data -l logfile start
db |
db | initdb: warning: enabling "trust" authentication for local connections
db | initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
db | waiting for server to start....2024-09-25 07:21:19.690 UTC [48] LOG: starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
db | 2024-09-25 07:21:19.693 UTC [48] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db | 2024-09-25 07:21:19.703 UTC [51] LOG: database system was shut down at 2024-09-25 07:21:19 UTC
db | 2024-09-25 07:21:19.707 UTC [48] LOG: database system is ready to accept connections
db | done
db | server started
db | CREATE DATABASE
db |
db |
db | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql
db | CREATE TABLE
db | INSERT 0 1
db | INSERT 0 1
db | INSERT 0 1
db | INSERT 0 1
db | INSERT 0 1
db |
db |
db | waiting for server to shut down...2024-09-25 07:21:20.019 UTC [48] LOG: received fast shutdown request
db | .2024-09-25 07:21:20.025 UTC [48] LOG: aborting any active transactions
db | 2024-09-25 07:21:20.026 UTC [48] LOG: background worker "logical replication launcher" (PID 54) exited with exit code 1
db | 2024-09-25 07:21:20.029 UTC [49] LOG: shutting down
db | 2024-09-25 07:21:20.033 UTC [49] LOG: checkpoint starting: shutdown immediate
db | 2024-09-25 07:21:20.157 UTC [49] LOG: checkpoint complete: wrote 931 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.082 s, total=0.128 s; sync files=304, longest=0.004 s, average=0.001 s; distance=4270 kB, estimate=4270 kB; lsn=0/1915BB0, redo lsn=0/1915BB0
db | 2024-09-25 07:21:20.162 UTC [48] LOG: database system is shut down
db | done
db | server stopped
db |
db | PostgreSQL init process complete; ready for start up.
db |
db | 2024-09-25 07:21:20.233 UTC [1] LOG: starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
db | 2024-09-25 07:21:20.233 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
db | 2024-09-25 07:21:20.233 UTC [1] LOG: listening on IPv6 address "::", port 5432
db | 2024-09-25 07:21:20.236 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db | 2024-09-25 07:21:20.240 UTC [66] LOG: database system was shut down at 2024-09-25 07:21:20 UTC
db | 2024-09-25 07:21:20.244 UTC [1] LOG: database system is ready to accept connections
api | running...
api | データベース接続成功
api | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
api |
api | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
api | - using env: export GIN_MODE=release
api | - using code: gin.SetMode(gin.ReleaseMode)
api |
api | [GIN-debug] GET /ping --> main.main.func1 (3 handlers)
api | [GIN-debug] GET /get --> main.main.func2 (3 handlers)
api | [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
api | Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
api | [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
api | [GIN-debug] Listening and serving HTTP on :8080
pgadmin4 | NOTE: Configuring authentication for DESKTOP mode.
pgadmin4 | pgAdmin 4 - Application Initialisation
pgadmin4 | ======================================
pgadmin4 |
pgadmin4 | ----------
pgadmin4 | Loading servers with:
pgadmin4 | User: pgadmin4@pgadmin.org
pgadmin4 | SQLite pgAdmin config: /var/lib/pgadmin/pgadmin4.db
pgadmin4 | ----------
pgadmin4 | Added 0 Server Group(s) and 1 Server(s).
pgadmin4 | postfix/postlog: starting the Postfix mail system
pgadmin4 | [2024-09-25 07:21:33 +0000] [1] [INFO] Starting gunicorn 20.1.0
pgadmin4 | [2024-09-25 07:21:33 +0000] [1] [INFO] Listening at: http://[::]:80 (1)
pgadmin4 | [2024-09-25 07:21:33 +0000] [1] [INFO] Using worker: gthread
pgadmin4 | [2024-09-25 07:21:33 +0000] [121] [INFO] Booting worker with pid: 121
http://localhost:8080/get
にアクセスしてみると、以下のレスポンスを得られ、データベースに挿入したテストデータが取得できます。
{
"id": "1",
"name": "a1_name",
"password": "a1_password"
}
http://localhost:81
にアクセスしてみると、PGAdminでDocker Compose実行時に挿入したテストデータを確認することができます。上記で取得したデータがデータベース内にも存在することが確認できますね。
リポジトリの説明
一応、ほぼすべてのディレクトリやファイルについて説明していますが、冗長な説明部分も多いため、実際にリポジトリをクローンして手元で触ってみて、よくわからない部分だけ説明を読む形を推奨します。
go-dev-template/
├── app/ # Go関連のファイルを置くディレクトリ
│ ├── tmp/ # Airのホットリロードのために自動生成される
│ ├── .air.toml # Airの設定ファイル
│ ├── go.mod # Goで使うモジュールの依存関係を管理するファイル
│ ├── go.sum # go.modの整合性を担保するためのファイル
│ └── main.go # APIサーバー起動時のエントリーポイントとなるファイル
├── config/
│ └── servers.json # PGAdminのための設定ファイル
├── sql/
│ └── init.sql # Docker起動時に実行するSQLファイル
├── docker-compose.yml # APIサーバー、DB、PGAdminの3つのコンテナに関する設定ファイル
└── go.dockerfile # GoのAPIサーバー用のDocker設定ファイル
docker-compose.yml
Goで実装するAPIサーバー、PostgreSQLのデータベース、PGAdminの3つのDockerコンテナを立ち上げるための設定ファイルです。
3つのコンテナを立ち上げた上で、各コンテナを接続するために、dockerネットワークを生成・設定してあげる必要があります。
docker network create api-db-network
docker network create db-pgadmin-network
今回のハッカソンでは、バックエンド開発の経験がほとんどないメンバーもいたため、できるだけコマンドラインでデータベースの確認などをしなくても良いようにしたいと考え、PGAdminというGUIでデータベースに対する操作を行えるツールを導入しました。
services:
api:
container_name: api
build:
context: .
dockerfile: go.dockerfile
ports:
- 8080:8080
depends_on:
- db
tty: true
volumes:
- ./app:/go/app
networks:
- api-db-network
db:
container_name: db
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_USER: db-user
POSTGRES_PASSWORD: db-password
POSTGRES_DB: db-name
volumes:
- db:/var/lib/postgresql/data
- ./sql:/docker-entrypoint-initdb.d
networks:
- api-db-network
- db-pgadmin-network
pgadmin4:
container_name: pgadmin4
image: dpage/pgadmin4:8.4
ports:
- 81:80
volumes:
- pgadmin-data:/var/lib/pgadmin
- ./config/servers.json:/pgadmin4/servers.json
environment:
PGADMIN_DEFAULT_EMAIL: user@example.com
PGADMIN_DEFAULT_PASSWORD: password
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
depends_on:
- db
networks:
- db-pgadmin-network
volumes:
db:
pgadmin-data:
networks:
api-db-network:
external: true
db-pgadmin-network:
external: true
go.dockerfile
GoのAPIサーバー用のDocker設定ファイルです。
app/をgo/app/にコピーして、作業用のディレクトリとしています。また、新たにgoのサードパーティ製パッケージを導入したい場合には、通常通りapp/下でgo install ~
もしくはgo get ~
とすれば、go.modファイルに追加されるため、Dockerコンテナ起動時に必要なパッケージすべてがインストールされます。
FROM golang:1.23.1-alpine3.20
COPY ./app /go/app/
WORKDIR /go/app/
RUN go mod download && go mod tidy
RUN go install github.com/air-verse/air@latest
CMD ["air", "-c", ".air.toml"]
EXPOSE 8080
app/
Goで実装するファイルはこのディレクトリ下に置いていきます。
ルートディレクトリに直接main.goをおいても良かったのですが、今後マイグレーション用で別モジュールを作りたくなった際などに便利だと考えたため、appディレクト下にまとめています。
tmp/
Airでホットリロードを有効にするため、Goファイルをビルドした実行ファイルなどが自動生成されます。
.air.toml
Airの設定を記述しています。
air init
で生成したファイルとほぼ同じです。
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "tmp/main.exe"
cmd = "go build -o ./tmp/main.exe ."
delay = 0
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = true
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true
main.go
基本的な処理しか記述していないため、詳細な説明は省略します。
データベースとしてはPostgreSQLを採用し、フレームワークにはGinを採用しています。
データベース接続に成功した上で、データベースからレコードを取得できることを確認するために、/getエンドポイントを用意しています。このエンドポイントを叩くとaccountsテーブルからレコードを1行取得し、レスポンスとして返します。
実際にこのリポジトリをテンプレートリポジトリとして使う場合には、データベース情報は環境変数として.envファイルなどに記載するようにしてください。
package main
import (
"database/sql"
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
// データベースからレコード取得する際に用いる変数
var (
id int
name string
password string
)
func main() {
// データベース接続
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
"db", "db-user", "db-password", "db-name", "5432")
db, err := sql.Open("postgres", dsn)
if err != nil {
panic("データベース接続失敗")
} else {
fmt.Println("データベース接続成功")
}
r := gin.Default()
// GET /get
r.GET("/get", func(ctx *gin.Context) {
row := db.QueryRow("SELECT id, name, password FROM accounts;")
if row.Err() != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": row.Err().Error()})
return
}
err := row.Scan(&id, &name, &password)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err})
return
}
ctx.JSON(http.StatusOK, gin.H{
"id": strconv.Itoa(id),
"name": name,
"password": password,
})
})
r.Run()
}
configディレクトリ
servers.jsonファイル
このファイルには、データベースに関する設定を記述することで、PGAdminでのログイン作業などを削減しています。
{
"Servers": {
"1": {
"Name": "db-name",
"Group": "Servers",
"Host": "db",
"Port": 5432,
"MaintenanceDB": "postgres",
"Username": "db-user",
"SSLMode": "prefer"
}
}
}
sqlディレクトリ
init.sqlファイル
Docker Compose実行時に、テーブルおよびテストデータを挿入するためのSQLファイルです。
accountsテーブルを作成し、4件のテストデータを挿入します。
CREATE TABLE accounts (
id SERIAL NOT NULL,
name VARCHAR(20) NOT NULL,
password VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO accounts (name, password) VALUES ('a1_name', 'a1_password');
INSERT INTO accounts (name, password) VALUES ('a2_name', 'a2_password');
INSERT INTO accounts (name, password) VALUES ('a3_name', 'a3_password');
INSERT INTO accounts (name, password) VALUES ('a4_name', 'a4_password');
トラブルシューティング
私の手元で環境構築する際に起こったエラーについて、解決策を記載しておきます。
Dockerのビルドに失敗する
> docker compose build
2024/09/25 13:29:24 http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed
[+] Building 1.5s (3/3) FINISHED docker:default
=> [api internal] load build definition from go.dockerfile 0.0s
=> => transferring dockerfile: 232B 0.0s
=> ERROR [api internal] load metadata for docker.io/library/1.23.1-alpine3.20:latest 1.5s
=> [api auth] library/1.23.1-alpine3.20:pull token for registry-1.docker.io 0.0s
------
> [api internal] load metadata for docker.io/library/1.23.1-alpine3.20:latest:
------
failed to solve: 1.23.1-alpine3.20: failed to resolve source metadata for docker.io/library/1.23.1-alpine3.20:latest: failed to authorize: failed to fetch oauth token: unexpected status from GET request to https://auth.docker.io/token?scope=repository%3Alibrary%2F1.23.1-alpine3.20%3Apull&service=registry.docker.io: 401 Unauthorized
対処する上で、こちらの記事が参考になりました。
Docker構成ファイルを削除
rm ~/.docker/config.json
Dockerキャッシュを削除
rm ~/.docker/buildx
Dockerログアウト
docker logout
Dockerログイン
docker login
Dockerコンテナの立ち上げに失敗する
> docker compose up
[+] Running 2/0
✔ Volume "go-dev-template_db" Created 0.0s
✔ Volume "go-dev-template_pgadmin-data" Created 0.0s
- Container db Creating 0.0s
Error response from daemon: Conflict. The container name "/db" is already in use by container "db0b3ae760659a491f77e4a91c38128804931d6618b836cb9ac6f5547b22a0a7". You have to remove (or rename) that container to be able to reuse that name.
以下の記事を参考に解決しました。
Dockerコンテナのリストを確認
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
db0b3ae76065 postgres:16 "docker-entrypoint.s…" 2 weeks ago Created db
1f81507429d9 mybrary-backend-api "air -c .air.toml" 3 weeks ago Exited (1) 3 weeks ago api
名前がコンフリクトを起こしていると言われたDockerコンテナを削除
> docker rm db0b3ae76065
db0b3ae76065
> docker rm 1f81507429d9
1f81507429d9
おわりに
まだまだGopherとなって日が浅いため、改善する余地があると思いますので、もっとこうした方が良いなどの意見がありましたら、コメントしていただけると嬉しいです。