はじめに
サイオステクノロジーの中島です。
gccを使用してビルドするパッケージがGo言語のプログラム内に含まれている際に、実行環境のglibcのバージョンが古いとエラーが起こります。とくに、Dockerのコンテナを作る際には、ビルド環境用のコンテナと実行用のコンテナが異なるときがよく見られる現象のためその解決方法について説明します。
実行用のサンプルについて
Go言語で、ginを使用してSQLite3を使用するサンプルプログラムして以下のソースを使用します。
package main
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
gorm.Model
Name string `json:"name"`
Age int `json:"age"`
}
type GetUser struct {
Name string `json:"name"`
Age int `json:"age"`
}
// gormを使いsqlite3でデータベースを作成する
func dbinit() {
db, err := gorm.Open("sqlite3", "test.sqlite3")
if err != nil {
panic("データベース開けず")
}
db.AutoMigrate(&User{})
}
// データベースにデータを追加する
func dbinsert(name string, age int) {
db, err := gorm.Open("sqlite3", "test.sqlite3")
if err != nil {
panic("データベース開けず")
}
defer db.Close()
db.Create(&User{Name: name, Age: age})
}
// 登録データを全て取得する
func dbGetAll() []User {
db, err := gorm.Open("sqlite3", "test.sqlite3")
if err != nil {
panic("データベース開けず")
}
defer db.Close()
var users []User
db.Order("created_at desc").Find(&users)
return users
}
func main() {
//ginの初期化
r := gin.Default()
dbinit()
//データの取得
r.GET("/users", func(c *gin.Context) {
users := dbGetAll()
c.JSON(200, users)
})
//jsonデータを受け取ってデータベースにデータを追加する
r.POST("/new", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
dbinsert(user.Name, user.Age)
c.JSON(200, gin.H{
"message": "ok",
})
})
r.Run()
}
上記プログラム実行後、データをcurlコマンドを使用して登録する場合は以下の通り実行します。
$ curl localhost:8080/new -X POST -d '{"name":"ab","age":10}'
{"message":"ok"}
また、登録したデータをcurlで確認する場合は以下の通り実行します。
$ curl localhost:8080/users
[{"ID":1,"CreatedAt":"2023-08-25T17:05:27.663728852+09:00","UpdatedAt":"2023-08-25T17:05:27.663728852+09:00","DeletedAt":null,"name":"ab","age":10}]
GLIBCエラーを起こすコンテナを作る
2023/08/25時点では以下のDocerfileでビルドを実行するとGLIBCエラーが起きます。
FROM golang:1.19 as builder
WORKDIR /app
RUN apt-get update && apt-get upgrade -y
ADD ./ .
RUN go build -o app
FROM debian:11
RUN apt-get update && apt-get upgrade -y
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
このコンテナをビルドし、以下のコマンドで実行すると以下の通り、GLBCの対象バージョンが見つからないエラーが発生します。
$ docker run -it --rm --name sqlitetest -p 8080:8080 sqlitetest:latest
./app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./app)
./app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./app)
./app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./app)
make: *** [Makefile:4: run] エラー 1
GLBCの原因調査について
上記エラーの発生原因を調べるには、以下のコマンドを実行することで、導入されているライブラリでGLIBCのバージョンを調べることができます。
strings /lib/x86_64-linux-gnu/libc.so.6 |grep GLIBC
ただ、公開されているコンテナの種類によってはstringsコマンドが導入されていない場合があるので、その場合は以下のコマンドでインストールができます。
apt install binutils
2023/8/26時点で、以下のコンテナのGLIBCの最新バージョンを確認すると以下の通りでした。
コンテナ種類 |
GLIBCの安定バージョン |
golang:1.19 |
2.36 |
debian:11 |
2.30 |
上記の通り、導入されているGLIBCのバージョンの差異があるためエラーが発生したことを突き止めました。
解決方法
GLIBのバージョンでコンテナごとの差異を解決する方法は以下の方法が考えられます。
- ビルドと実行のコンテナを同じにする
- 実行コンテナのGLIBCのバージョンを上げる
上記二種類の具体的な方法は以下に示します。
ビルドと実行のコンテナを同じにする
以下のDockerfileを使用して、ビルドツールのコンテナを作ってビルドすれば解決は可能です。
FROM debian:11 as builder
WORKDIR /app
RUN apt-get update && apt-get upgrade -y && apt-get install -y curl gcc
RUN curl -OL https://go.dev/dl/go1.19.12.linux-amd64.tar.gz &&\
tar -C /usr/local -xzf go1.19.12.linux-amd64.tar.gz &&\
rm -rf go1.19.12.linux-amd64.tar.gz
ENV PATH $PATH:/usr/local/go/bin
ADD ./ .
RUN go build -o app
FROM debian:11
RUN apt-get update && apt-get upgrade -y
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
上記コンテナをビルド後、コンテナを実行すれば、go言語のプログラムが起動して、8080ポートで待ち受けを実施します。
実行コンテナのGLIBCのバージョンを上げる
aptコマンドを使用してGLIBCのバージョン上げるには以下の実験的なリポジトリを登録し、インストールすれば新しいバージョンがインストールされます。
deb http://ftp.debian.org/debian experimental main
なお、2023/08/25時点で上記リポジトリを使用してインストールした場合は、2.38が導入されます。
リポジトリを/etc/apt/sources.listに登録後、GLIBCのバージョンを上げる場合は以下の通りコマンドを実行します。
apt-get update
apt-get -t experimental install -y libc6
上記を導入したDockerfileを作る場合は以下の通りになります。
FROM golang:1.19 as builder
WORKDIR /app
RUN apt-get update && apt-get upgrade -y
ADD ./ .
RUN go build -o app
FROM debian:11
RUN cp /etc/apt/sources.list /etc/apt/sources.list.bak
RUN echo "deb http://deb.debian.org/debian experimental main" >> /etc/apt/sources.list
RUN apt-get update && apt-get upgrade -y && apt-get -t experimental install -y libc6
RUN cp /etc/apt/sources.list.bak /etc/apt/sources.list && rm /etc/apt/sources.list.bak
RUN apt-get update && apt-get clean
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
上記コンテナをビルド後、コンテナを実行すれば、go言語のプログラムが起動して、8080ポートで待ち受けを実施します。
まとめ
GLIBCのバージョン差異で、Go言語の実行プログラムが実行できない問題が結構発生します。
インターネットで検索を実施すると「CGO_ENABLED=0」してビルドすることを推奨していますが、Go言語のプログラムないでgccによるビルドが必要なものが含まれているとこれでは解決しないので、解決方法一つとして参考にしたらよいと考えます。