基于Docker Alpine镜像+Go源码构建Golang

本着折腾,基于源码编译Golang!

1. Go源码编译安装的介绍

Golang的通常安装方式是基于官网下载一个指定操作系统(Linux/Mac/Windows)版本的编译安装好的包,亦或者是基于SourceCode源码安装(Git或者下载指定版本的源码包);

针对Golang的源码编译安装,又有两种方式:

  1. 基于gc Go Compiler编译器,即Go语言进行自举编译安装(gcc支持的go最后一个版本是1.4),细节参见:https://golang.org/doc/install/source
  2. 基于gccgo(使用GCC后端的更传统的编译器), 细节参见:https://golang.org/doc/install/gccgo

以下主要基于Go编译器安装Go语言环境,同时当指定好GOOSGOARCH后,这块也可以支持跨平台交叉编译。

2. Go编译器和工具安装说明

Go工具链是用Go编写的,要构建它,需要安装Go编译器;由于我们没有GO编译器,同时1.4以后的GO语言版本没有直接支持GCC,支持GO编译器GCCGO,因此我们需要先现在1.4版本的GO,利用GCC编译好GO编译器后,再利用1.4版本的GO编译器编译最新版本的GO(比如以下的1.13);

注意,进行工具初始构建的脚本在$GOROOT_BOOTSTRAP中查找现有的Go工具链,如果未设置,则GOROOT_BOOTSTRAP的默认值为$HOME/go1.4

3. 源码编译安装

  1. 下载go1.4版本,源码从官方下载
  2. 下载go1.13版本
  3. 进入到docker alpine容器内(其他的Linux环境流程类似)
  4. 先编译go1.4版本
  5. 利用go1.4的go编译器,编译go1.13
  6. 在第5不可以支持交叉编译:usage: GOOS=os GOARCH=arch ./bootstrap.bash
// Go 1.4编译的依赖环境安装
apk update && apk add bash gcc musl-dev

// 解压go1.4,源码从官方下载
tar -zxf go1.4-bootstrap-20171003.tar.gz
mv go /usr/local/go1.4
// 解压go1.13版本
tar -zxf go1.13.3.src.tar.gz
mv go /usr/local/go

// 环境变量设置(GOROOT_BOOTSTRAP是编译器环境设定需要)
echo 'export GOROOT_BOOTSTRAP=/usr/local/go1.4' >> ~/.bash_profile
echo 'export GOROOT=/usr/local/go' >> ~/.bash_profile
echo 'export GOPATH=/data/go' >> /data/go
echo 'export GOPATH=/data/go' >> ~/.bash_profile
echo 'export PATH=${GOROOT}/bin:${PATH}' >> ~/.bash_profile

// 重载环境配置变量(主要是重载GOROOT_BOOTSTRAP)
source ~/.bash_profile

// 编译go1.4的版本
cd /usr/local/go1.4/src && ./make.bash

// 编译go1.13版本
cd /usr/local/go1.13/src && ./make.bash

// 版本检测
go version

// 编译不同操作系统的Go,相关GOOS和GOARCH的支持,查看:https://golang.org/doc/install/source
cd /usr/local/go1.13/src
GOOS=linux GOARCH=amd64 ./bootstrap.bash

4. 流程转换成Dockerfile

具体可以参见:

Tips: 注意,这块我把go源码Clone到了本地目录,这块也可以直接在Dockerfile的Run中修改,这样就不用Copy的相关部分了!

# Build Golang
FROM alpine:latest as builder

# ARG Setting
ARG VERSION=1.13
ARG GOOS=""
ARG GOARCH=""

# ENV Setting
LABEL Description="Build Go on alpine" Version="Go version $VERSION"

# Go source
COPY ./go-source /usr/local/go

# Building
RUN set -eux; \
    # Go 1.4编译的依赖环境安装
    apk add --no-cache --virtual .build-deps \
        bash gcc musl-dev git; \
    # 编译go1.4的版本
    export GOROOT_BOOTSTRAP=/usr/local/go1.4 \
        INSTALL=/usr/local \
        GOROOT=/usr/local/go \
    ; \ 
    cd $INSTALL \
        #&& git clone https://github.com/golang/go.git
        && cp -a go go1.4 && cd go1.4/src \
        && git checkout release-branch.go1.4 \
        && ./make.bash \
    ; \
    # 编译指定版本go
    cd $GOROOT/src \
        && git checkout release-branch.go$VERSION \
        && if [[ "$GOOS" != "" && "$GOARCH" != "" ]]; then \
            GOOS=$GOOS GOARCH=$GOARCH ./bootstrap.bash; else ./make.bash; fi \
        # 移除相关依赖
        && apk del .build-deps \
        && rm -rf $GOROOT_BOOTSTRAP \
        && rm -rf $GOROOT/.git \
        && $GOROOT/bin/go version

# Build a clean Go image
FROM alpine:latest

# 复制构建完成的最新版本Go到当前镜像
COPY --from=builder /usr/local/go /usr/local/go

# Go 编译环境初始化
ENV GOPROXY=https://goproxy.io \
    GOROOT=/usr/local/go \
    GOPATH=/go 
ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH

# 其他初始化相关
RUN echo "alias ll='ls -al'" >> ~/.profile \
    && echo "export PATH=$GOPATH/bin:$GOROOT/bin:$PATH" >> ~/.profile

# 指定Volume和Workdir
VOLUME ["/data/go"]
WORKDIR "/data/go"

5. 官方alpine版本Dockerfile文件

查看docker-library/golang下的alpine版本的Dockerfile文件,可以发现其实通过apk安装完go后,再下载安装指定的Go版本。

这一步操作等同于利用gcc编译好go1.4,在利用go编译最新版本的go,这块可以按自己的需要自行引入!

详细参见:https://github.com/docker-library/golang/blob/a4deea14ce3306822bb9352ccf124af8c0eea257/1.13/alpine3.10/Dockerfile

// 如果采用直接安装go,则无需下载go1.4版本的go,并编译安装,可以直接利用apk安装apk库中的go
RUN set -eux; \
    apk add --no-cache --virtual .build-deps \
        bash \
        gcc \
        musl-dev \
        openssl \
        go \
    ; \
...

6. 自建Dockerfile的坑点

6.1. 调试相关

// 调试相关,可以显示更多的信息
-u  :默认不激活。若激活后,当使用未配置变量时,会显示错误信息;
-v  :默认不激活。若激活后,在信息被输出前,会先显示信息的原始内容(未做变量解析);
-x  :默认不激活。若激活后,在命令被运行前,会显示命令内容(做了变量解析,前面有 ++ 符号)

RUN set -eux

6.2. 降低镜像大小

  1. COPY、RUN会创建镜像层,拷贝之前的层内容会叠加,导致镜像很大(解决方案,采用多阶段构建;排查镜像过大,可以通过docker history 镜像ID分析哪些层导致镜像增大)
  2. apk安装依赖,构建完成移除依赖,如果基于多阶段构建,可以不用考虑这块!
// 加入依赖内容
RUN set -eux; \
    # Go 1.4编译的依赖环境安装
    apk add --no-cache --virtual .build-deps \
        bash gcc musl-dev git; \
    ...
// 移除依赖内容
        && apk del .build-deps \
        && rm -rf $GOROOT_BOOTSTRAP \
        && rm -rf $GOROOT/.git \
    ...

6.3. ENV的问题

  1. ENV在构建阶段中所有后续指令的环境中使用,并且在许多情况下也可以内联替换。
  2. 环境变量在同一行,无法立即生效,正常情况下,执行export a=1 b=2 c=$a,c=2,但在Dockerfile中以下有差别!
// PATH, it not works:
ENV GOPROXY=https://goproxy.io \
    GOROOT=/usr/local/go \
    GOPATH=/go \
    PATH=$GOPATH/bin:$GOROOT/bin:$PATH

// PATH, it works:
ENV GOPROXY=https://goproxy.io \
    GOROOT=/usr/local/go \
    GOPATH=/go
ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH

6.4. bash shell提示问题

以下命令,让.profile生效的话,需要基于登陆shell,即docker run --rm -it go-builder sh -l

// Dockerfile中的alias命令添加
RUN echo "alias ll='ls -al'" >> ~/.profile \
    echo "export PATH=$GOPATH/bin:$GOROOT/bin:$PATH" >> ~/.profile

7. 最后

如果不想自己Build,最容易的方式,直接拖go-alpine镜像,大小其实和自行编译的差不多,而且还省事省力!

$ docker images|grep golang
golang                  1.13-alpine         f23ef2e47d30        8 days ago          359MB
//docker images|grep builder,对比以下....
go-builder              latest              85783217bc33        42 minutes ago      358MB

最后,清理下相关现场:

$ docker image prune
$ docker container prune