利用docker实现go程序的快速部署
最近很长一段时间一直通过各种渠道去了解国内外有关devops方面的实践,感受很多的知识点都特别的分散,所以想通过系统的整理来巩固相应的知识体系。接下来会撰写一系列有关容器化的文章。
1、 利用docker部署一个简单的go程序,并且利用阿里云的平台,进行镜像的生成
2、 利用docker-compose部署一个复杂的go程序,同时部署包含多个不同子容器的集成
3、 利用gitlab和Harbor来做ci/cd
容器是独立的软件包,能够将应用程序以及所有的依赖项、工具、库、配置文件 以及运行该应用程序所需要的所有的其他内容捆绑在一起。
通过docker可以实现将您 的应用环境从运行的物理操作系统上面抽象一层虚拟操作环境。从而保证你的程序从开发到测试以及现场的部署,保证了环境 的一致性问题。
创建一个简单的Go项目
在开始演习之前,我们通过下面命令来创建一个新的目录,接下来我们所有的文件都将存放在这个目录当中。
mkdir go-docker
在创建完成目录之后,我们需要用go的原生命令来初始化相应的go模块。go mod是go11新增加的功能,在这之前出现很好几种模块化的解决方案,在go11之后官方终于给出了自己的解决方案.
cd go-docker
go mod init github.com/wenchangshou2/go-docker
我们现在创建一个简单的hello world的服务,通过下面的命令来创建一个新的文件
touch hello_server.go
以下是hello_server.go的内容
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gorilla/mux"
"gopkg.in/natefinch/lumberjack.v2"
)
func handler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
name := query.Get("name")
if name == "" {
name = "访客"
}
log.Printf("接收一个新的请求 %s\n", name)
w.Write([]byte(fmt.Sprintf("Hello, %s\n", name)))
}
func main() {
// 创建服务和root事件
r := mux.NewRouter()
r.HandleFunc("/", handler)
srv := &http.Server{
Handler: r,
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 配置日志
LOG_FILE_LOCATION := os.Getenv("LOG_FILE_LOCATION")
if LOG_FILE_LOCATION != "" {
log.SetOutput(&lumberjack.Logger{
Filename: LOG_FILE_LOCATION,
MaxSize: 500, // 总大小
MaxBackups: 3,
MaxAge: 28, //最长保存时间
Compress: true, // 压缩数据,默认关闭
})
}
// 启动服务
go func() {
log.Println("启动服务")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}()
// 正常关闭
waitForShutdown(srv)
}
func waitForShutdown(srv *http.Server) {
interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
// 阻塞,直到接收到信号
<-interruptChan
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
srv.Shutdown(ctx)
log.Println("结束退出")
os.Exit(0)
}
通过下面的命令来构建一个应用程序
wcs@iMac ~/demo/go-docker go build ✔ >4950 23:22:27
go: finding github.com/gorilla/mux v1.7.4
go: downloading github.com/gorilla/mux v1.7.4
go: extracting github.com/gorilla/mux v1.7.4
go: downloading gopkg.in/natefinch/lumberjack.v2 v2.0.0
go: extracting gopkg.in/natefinch/lumberjack.v2 v2.0.0
通过go build,会自动下载需要的包,并且生成相应的可执行程序,通过下面的命令运行
wcs@iMac ~/demo/go-docker ./go-docker ✔
2020/02/26 23:24:05 启动服务
现在我们尝试通过curl 来测试功能是否可用
wcs@iMac ~ curl localhost:8080 ✔ 4958
Hello, 访客
wcs@iMac ~ curl http://localhost:8080\?name\=Rajeev
Hello, Rajeev
通过DockerFile文件来定义docker 镜像文件
现在我们需要在上面的根目录下面新建一个文件,将文件的名称命名为Dockerfile.
# 构建该项目的基础镜像
FROM golang:latest
# 添加作者信息
LABEL maintainer="wcs <wenchangshou@gmail.com>"
# 设置当前的工作目录
WORKDIR /app
# 复制 go mod和sum 文件
COPY go.mod go.sum ./
# 下载所有的依赖
RUN go mod download
# 复制当前目录源文件到工作目录下面
COPY . .
# 编译GO程序
RUN go build -o main .
# 通过EXPOSE对外暴露服务的端口号
EXPOSE 8080
# 通过下面的命令来运行可执行文件
CMD ["./main"]
编译和运行docker镜像
上面我们已经定义了DockerFile,现在我们需要通过命令将DockerFile生成构建和运行相应的映像
编译镜像
docker build -t go-docker .
你懂的国内应该各种原因,访问会特别的慢,你可以利用aliyun和daocloud来进行回事。
通过下面的命令来查看镜像是否生成成功.
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker lates 7654e880c09b 2 minutes ago 826MB
golang latest c3474fb0f20e 15 hours ago 809MB
运行docker镜像
现在我们终端可以运行之前生成的镜像了
docker run -d -p 8888:8080 go-docker
3c80b9cb6b21a4fd50ea8b157ace4f3a9757e92b0e3cf9192639d30299d03d73查看运行的容器
你可以通过下面的命令来查看正在运行的容器
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c80b9cb6b21 go-docker "./main" 45 seconds ago Up 45 seconds 0.0.0.0:8888->8080/tcp quirky_shamir
测试容器功能
现在我们可以通过curl来测试应用是否正常
curl http://localhost:8888\?name\=wcs
Hello, wcs
停止容器
现在运行完成之后我们就可以停止相应的容器。在每次创建容器的时候都会产生一个唯一的id,那个id用来标识相应的容器,之后可以通过该id进行相应的操作
docker container stop 3c80b9cb6b21
挂载本地目录到docker镜像中
现在我们开始一个新的实例,在dockerfile里面提供了一个volume的字段,通过该字段可以定义容器内部的共享的目录。定义完成之后,你可以在创建容器的时候指定volume目录与本地目录的绑定。
在以下Dockerfile中,我们声明一个volume路径/app/logs。容器会将日志文件写入/app/logs/app.log。运行docker映像时,我们可以将宿主的目录挂载到该卷上。完成此操作后,我们将能够从宿主的已挂载目录访问所有日志文件。
# 构建该项目的基础镜像
FROM golang:latest
# 添加作者信息
LABEL maintainer="wcs <wenchangshou@gmail.com>"
# 设置当前的工作目录
WORKDIR /app
# 编译参数
ARG LOG_DIR=/app/logs
# 创建log目录
RUN mkdir -p ${LOG_DIR}
#环境变量
ENV LOG_FILE_LOCATION=${LOG_DIR}/app.log
# 复制 go mod和sum 文件
COPY go.mod go.sum ./
# 下载所有的依赖
RUN go mod download
# 复制当前目录源文件到工作目录下面
COPY . .
# 编译GO程序
RUN go build -o main .
# 通过EXPOSE对外暴露服务的端口号
EXPOSE 8080
# 声明Volumes
VOLUME [${LOG_DIR}]
# 通过下面的命令来运行可执行文件
CMD ["./main"]
我们重新编译一个新的镜像
docker build -t go-docker-voluume -f Dockerfile.volume
现在我们创建宿主将要绑定的目录
mkdir -p ~/logs/go-docker
docker run -d -p 8889:8080 -v ~/logs/go-docker:/app/logs go-docker-volume
394433aa0804dd24443d65090006e8becbe2aa26a883efe4324e79d4e085e261
现在我们发现docker已经将日志写入到宿主对象的目录当中
tail -f ~/logs/go-docker/app.log
2020/02/26 16:01:38 启动服务
优化镜像
通过上面的示例,我们生成了二个镜像,但是你们仔细查看,会发现即使这么小功能的一个程序,也会占用很大的存储空间。
docker image ls ✔ 4992 00:04:52
REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker-volume latest 0b534f6bceaf 4 minutes ago 826MB
go-docker latest 7654e880c09b 19 minutes ago 826MB
golang latest c3474fb0f20e 16 hours ago 809MB
仅golang:latest镜像就占用了809MB,,而我们生成程序也占用了826M的空间。
上面之所以这样大,是因为golang:latest里面包含了整个go的运行环境以及相应的编译环境,而我们的应用程序仅在编译时需要这些,在编译完成之后仅运行就可以。根据这些需求我们可以将原有的镜像拆分成多步,第一步是编译,之后我们可以通过Alpine linux来进行最小化运行。
Dockerfile.multistage
FROM golang:latest as builder
LABEL maintainer="wcs<wenchangshou@gmail.com>"
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# 开始一个新的步骤
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 复制上一阶段的预构建二进制文件
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
现在我们生成新的镜像
docker build -t go-docker-optimized -f Dockerfile.multistage .
现在我们再看压缩后的镜像
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go-docker-optimized latest ed8e431271dc 23 seconds ago 14.1MB
go-docker-volume latest 0b534f6bceaf 11 minutes ago 826MB
go-docker latest 7654e880c09b 27 minutes ago 826MB
golang latest c3474fb0f20e 16 hours ago 809MB
alpine latest e7d92cdc71fe 5 weeks ago 5.59MB
现在你惊讶的发现压缩后的镜像仅有14.1M
来源:oschina
链接:https://my.oschina.net/u/215677/blog/3175576