はじめに
今回はVSCodeの【Remote-Container機能】と【Docker】を使用した、
「開発用コンテナ」が便利ということで環境構築を行いました。
最小構成から自分がやりたいことを増やしていく中で、
つまづいた点・検索してもなかなか効果的なサイトが見つからなかった点についてまとめてみました。
この記事で分かること
- VSCodeのRemote-Container機能を使用した開発用コンテナ作成手順
- GoおよびMySQLの
docker-compose & Dockerfile
devcontainer.json
の設定で注意する点 - 上記2点を含む、webアプリ開発用Go+DBコンテナのソースコード
環境とコンテナ起動までの流れ
動作環境
Docker | Docker Desktop for Windows 4.12.0 |
Dockerのbackend | WSL2 Ubuntu 20.04 LTS |
IDE | VSCode(latest) |
ホストOS | Windows11 |
コンテナ起動までの流れ
事前に必要なもの
- WSL2
- Docker Desktop
- VSCode
3点のインストールと最低限の設定は必要です。
やり方に関しては本記事では取り扱いません。
WSL導入まで知りたい方はコチラを → 【#50 エンジニア転職学習】WSL2でUbuntu20.04のGo開発環境をつくる 参考に構築ください。
Docker Desktopに関しては公式マニュアル → Windows に Docker Desktop をインストール
どおりに実行すれば問題なくインストールまでいけるはずです。
開発用コンテナ起動までの全体流れ
上記【事前に必要なもの】のセットアップまで終わっている状態からスタートとします。
手順6に記述している通り、.devcontainer
が直下にあるディレクトリからReopen in Containerを実行してください。
違う階層で実行するとVSCodeが.devcontainer
を認識してくれないらしく、
設定した各種ファイルが反映されなかったり、指定したコンテナが起動しなかったりします。
-
WindowsTerminalでWSL2のUbuntuシェルを開き、任意のプロジェクトディレクトリ作成
-
cd [プロジェクト]
でプロジェクトに移動してcode .
を実行、WSLからVSCodeを開く -
.devcontainer
フォルダ作成 -
docker-compose.yml
,Dockerfile
,devcontainer.json
ファイルの作成(後ほど詳しく解説) -
Docker Desktopを起動しておく
-
.devcontainer
が直下にあるフォルダ(私の例では/build)でcode .
をしてVSCodeを開きなおす -
F1
を押す -
すぐに新しいVSCodeウィンドウが立ち上がり、開発用コンテナ起動(初回は結構時間かかります)
完成したコードと各ポイント
各コードとポイントを解説します。
今回のRemoteContainerを使用した開発用コンテナに関しては、人によって記述が違うことが多々あったため、
VSCodeの公式ドキュメントやDockerの公式ドキュメントをメインに参照していきました。
プロジェクト構成
全体像になります。
.
├── build
│ ├── .devcontainer
│ │ ├── devcontainer.json
│ │ └── docker-compose.yml
│ ├── golang
│ │ └── Dockerfile
│ └── mysql
│ ├── .env
│ ├── Dockerfile
│ └── conf
│ └── my.cnf
├── cmd
│ ├── app
│ │ ├── controllers
│ │ │ ├── route.go
│ │ │ └── webserver.go
│ │ ├── models
│ │ │ ├── albumdb.go
│ │ │ └── base.go
│ │ └── views
│ │ ├── index.html
│ │ ├── keyalbumlist.html
│ │ └── update.html
│ └── main.go
├── go.mod
└── go.sum
Goに関するプロジェクト構成で公式っぽいものはありますが、
正確ではないとGo開発チームから異議が唱えられているそうです。(Go のパッケージ構成のデファクトはあるのか?)
JAVAプロジェクトに見えないように、「src」ではなく「cmd」を使用しましょうとのことなのでその点だけ守ってます。
それ以外の構成はコチラ→ (The Go Blog Package names) の内容を開発進めながら身に付けていくしかないのかと思います。
.devcontainer
Remote-Containerで開発用コンテナをビルドするためのディレクトリです。
devcontainer.json
開発用コンテナとRemote時のVSCodeの設定をメインにするファイルです。
Microsoftの開発チームがGitで公開している → microsoft/vscode-dev-containers をベースにしています。
Docker-Composeを使用してコンテナを立ち上げるため、dockerComposeFile
での指定は必須です。
{
// リモートコンテナ名 適当でok
"name": "go-db-devcontainer",
// docker-composeファイルのパス指定 今回は同階層にあるためファイル名のみ記述
"dockerComposeFile": [
"docker-compose.yml"
],
// docker-compose.ymlの中で開発用コンテナとして作業するサービスを指定
"service": "app",
// リモートコンテナのVSCodeで実際に作業するディレクトリ
"workspaceFolder": "/workspace",
// VSCodeを閉じたときの動作指定
"shutdownAction": "stopCompose",
// 非ルートユーザーの作成
"remoteUser": "vscode",
// VSCodeの設定を記述 .vscodeフォルダの代わりになる(らしい)
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go",
"go.goroot": "/usr/local/go"
},
// VSCodeのExtensionsで追加したパッケージを指定
"extensions": [
"golang.Go"
]
}
docker-compose.yml
通常のDocker-Composeファイルです。今回はGo言語とMySQL用のコンテナビルドを指示してます。
version: '3.8'
services:
app:
user: vscode
# Go用のDockerfileがあるpath、dockerfile名を指示
build:
context: ../golang
dockerfile: Dockerfile
env_file:
- ../mysql/.env
# dbコンテナが起動した後に起動するように指示
depends_on:
- db
# プロジェクトフォルダのpathを指定して、workspace(開発用コンテナ内の作業場所)にマウント
volumes:
- ../..:/workspace:cached
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
# 何用かは分からないですがGoで開発する際のデバッガーとして必要らしいです
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
# appコンテナへのポートフォワード指定
ports:
- 8080:8080
# コンテナ切断をしないように指示
command:
/bin/sh -c "while sleep 1000; do :; done"
/bin/bash
db:
container_name: db
# MySQL用のDockerfile指定
build:
context: ../mysql
dockerfile: Dockerfile
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
platform: linux/x86_64
# envファイルのpath
env_file:
- ../mysql/.env
# データ永続化
volumes:
- mysql_data:/var/lib/mysql
- ../mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf
ports:
- 13306:3306
# コンテナ間の接続を可能にするためのネットワーク設定
networks:
- default
volumes:
mysql_data:
driver: local
networks:
default:
Dockerfile(Go用)
Goアプリ用のDockerfileです。ポイントは2点あります。
- 最後の
sudo chmod -R a+rwX /go/pkg/
を入れておく -
RUN
は&&
で繋いで処理を軽くする(個別にRUNを記述しない)
特にパーミッション変更は、開発時にサードパーティパッケージ等を使用する際に
go mod
への追加変更をするために必要ですので忘れずいれておきましょう。
FROM golang:1.19
ENV GO111MODULE on
WORKDIR /workspace
# コンテナ内の非ルートユーザの設定
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# 非ルートユーザー作成
# sudoコマンドの追加
# Go用の開発ツール
# go mod用のパーミッション変更 など
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& apt update \
&& apt install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME \
&& go install golang.org/x/tools/gopls@latest \
&& go install github.com/go-delve/delve/cmd/dlv@latest \
&& go install github.com/ramya-rao-a/go-outline@latest \
&& go install github.com/fatih/gomodifytags@latest \
&& go install github.com/josharian/impl@latest \
&& sudo chmod -R a+rwX /go/pkg/
mysql
既に似たようなコンテナがあると正常に起動されない時があるようです。
そのときはdocker system prune
とdocker system prune --volume
を実行して、
Dockerをきれいにしましょう。私の場合はそれで問題なく起動しました。
my.cnf
文字コード指定やプラグイン方法の指定をしています。
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin
default-time-zone = SYSTEM
log_timestamps = SYSTEM
# mysql8.0以降であれば必須↓
default-authentication-plugin = mysql_native_password
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
.env
環境変数としてDatabaseやパスワードの記述を行います。
# 任意の値にしてください
MYSQL_DATABASE=test1
MYSQL_USER=dev_user
MYSQL_PASSWORD=password
MYSQL_ROOT_PASSWORD=root_password
Dockerfile(MySQL用)
FROM mysql:8.0
EXPOSE 3306
ENV LANG ja_JP.UTF-8
cmd
アプリのGoファイル等を置いています。
ベースは以前作成したGin等をしようしたCRUDアプリ(【#46 エンジニア転職学習】GinTutorialのWebApp改造 HTMLに連動させる)になります。
内容自体は変わっていないので、コンテナ化に際して変更した点のみ紹介します。
route.go
最後のrouter.Run(:8080)
は以前までなら(localhost:8080)
でしたが、
コンテナ化に伴いlocalhost部分は消します。
docker-comopose.yml
でポートフォワーディングしているので、ブラウザでlocalhost:8080
を実行すればコンテナからレスポンスが返ってきます。
package controllers
import (
"net/http"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
func NewRoutes(dbConn *gorm.DB) {
//dbConnをインターフェースとして持つalbumhandlerを定義
albumHandler := AlbumHandler{
Db: dbConn,
}
//gin.Engineインターフェースを作成
router := gin.Default()
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"PUT", "PATCH", "DELETE", "POST", "GET"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "*"
},
MaxAge: 12 * time.Hour,
}))
//pattern一致するhtmlファイルをロードして、rendererに渡す
router.LoadHTMLGlob("app/views/*.html")
router.GET("/albums", func(c *gin.Context) {
albums := albumHandler.GetAlbums()
c.HTML(http.StatusOK, "index.html", gin.H{"albums": albums})
})
router.GET("/albums/:id", albumHandler.GetAlbumByID)
router.POST("/albums/new", albumHandler.InsertAlbums)
router.POST("/albums/update/:id", albumHandler.UpdateAlbumByID)
router.POST("/albums/delete/:id", albumHandler.DeleteAlbumByID)
router.POST("/albums/search", albumHandler.GetAlbumsByKeyword)
router.Run(":8080")
}
base.go
dsn
変数部分を2点変更。
- MySQLの
.env
ファイルで設定したUSER
,PASSWORD
,DATABASE
を使用 - @のあとに
tcp(db:3306)
の接続方法追加
package models
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
//Initialized DB and retrun gormDBInterface
func DbInit() *gorm.DB {
var dsn = "dev_user:password@tcp(db:3306)/test1?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open("mysql", dsn)
if err != nil {
panic("failed connecting DB")
}
db.LogMode(true)
db.AutoMigrate(&Album{})
return db
}
main.go
他の個人packageを使用しているファイルも同様ですが、
importをローカルファイル指定からGitHubリポジトリ指定に変更。
package main
import (
"github.com/XXXXXXXXXXX.git/cmd/app/controllers"
"github.com/XXXXXXXXXXX.git/cmd/app/models"
)
func main() {
dbConn := models.DbInit()
controllers.NewRoutes(dbConn)
}
実際に起動してみる
手順7まで行き、F1
を押しRemote-Containers:Reopen in Container
を実行。
VSCodeが開きなおしされ、下図のようにコンテナverに切り替わっていれば成功です。(初回は時間かかります)
試しにブラウザからリクエスト送信すると、、
↓のように無事レスポンスが返ってきました!!
おわりに
今回はRemote-Containerの環境構築を行いました。
Dockerをはじめ技術面的な学習はもちろん、logを見てエラー原因の仮説立てすることや公式ドキュメントを読むことの大切さを学習できたと思います。
個人的な都合で1か月振りの投稿となりましたが、またこれから再開していきたいと思います。
アドバイスや質問等ございましたらお願い致します。