你以为 Alpine 镜像想用就能用?

探讨使用Alpine镜像构建Golang应用时遇到的兼容性问题,特别是对于基于Gin框架的Web应用,指出使用alpine-glibc镜像可解决部分问题。

你以为 Alpine 镜像想用就能用?🌚

1 大致流程

  • alpine 镜像体积只有 5MB,作为 Docker 下最小的 Linux 镜像,很适合打造一些轻量级镜像。但 alpine 底层使用 musl-libc,兼容性与 glibc 有一定差距。
  1. 用 Golang 编写了一个简单的 hello, world 程序, Dockerfile 使用 alpine 作为基础镜像 FROM alpine:latest 能够正常运行;
  2. 基于 Gin 框架 Web 应用,相似的 Dockerfile 文件,build 完成后 run 的时候报了一些不明原因的错误,最后基础镜像改为 centos 或 alpine-glibc 后 (FROM centos:latest) 重新构建的镜像可以正常运行。

2 问题复现

2.1 使用 alpine 作为基础镜像构建 Golang “hello, world” 应用, ENTRYPOINT 使用 sh 运行程序

2.1.1 编写 “hello, world” Dockerfile,基础镜像 alpine

hello.go

package main

import (
	"fmt"
	"os"
	"flag"
)

var config = flag.String("config", "go & docker", "[-config value]")

func main() {
	flag.Parse()
	fmt.Printf("os.Args: %s\n", os.Args)
	fmt.Printf("hello, %s\n", *config)
}
FROM alpine:latest
ENV PARAMS=""
WORKDIR /
ADD hello /
ENTRYPOINT [ "sh", "-c", "./hello $PARAMS" ]
2.1.2 构建镜像

docker build -t hellogo:0.9 .

Sending build context to Docker daemon  2.152MB

Step 1/5 : FROM alpine:latest
 ---> 961769676411
Step 2/5 : ENV PARAMS=""
 ---> Using cache
 ---> 3d8a4e790bbf
Step 3/5 : WORKDIR /
 ---> Using cache
 ---> f693abea2696
Step 4/5 : ADD hello /
 ---> Using cache
 ---> 156c71a0b934
Step 5/5 : ENTRYPOINT [ "sh", "-c", "./hello $PARAMS" ]
 ---> Using cache
 ---> c5d4aa11a311
Successfully built c5d4aa11a311
Successfully tagged hellogo:0.9
2.1.3 运行

docker run --rm hellogo:0.9

输出结果

os.Args: [./hello]
hello, go & docker

由此可见,运行 hello, world 没啥问题

2.2 使用 alpine 作为基础镜像构建web应用, ENTRYPOINT 使用 sh 运行程序

2.2.1 编写 Dockerfile,基础镜像 alpine
FROM alpine:latest
WORKDIR /
ENV PARAMS=""
ADD orderserver /
ADD configs /configs
EXPOSE 60001
ENTRYPOINT ["sh", "-c", "/orderserver $PARAMS"]
2.2.2 构建镜像

执行命令docker build -t orderserver:build-with-alpine .

Sending build context to Docker daemon  23.83MB

Step 1/7 : FROM alpine:latest
 ---> 961769676411
Step 2/7 : WORKDIR /
 ---> Running in 4e77f58acc2a
Removing intermediate container 4e77f58acc2a
 ---> 576bb22ca7c6
Step 3/7 : ENV PARAMS=""
 ---> Running in 7d601c97afad
Removing intermediate container 7d601c97afad
 ---> d1bbcbd11e4f
Step 4/7 : ADD orderserver /
 ---> e804183d1957
Step 5/7 : ADD configs /configs
 ---> 5f22f0e5e099
Step 6/7 : EXPOSE 60001
 ---> Running in 849525af1bad
Removing intermediate container 849525af1bad
 ---> 354f12aa7c3d
Step 7/7 : ENTRYPOINT ["sh", "-c", "/orderserver $PARAMS"]
 ---> Running in 4119e9b8543a
Removing intermediate container 4119e9b8543a
 ---> 40c84a5e3673
Successfully built 40c84a5e3673
Successfully tagged orderserver:build-with-alpine
2.2.3 启动镜像

docker run -P --rm --name orderserver0 -v /docker/data/have-you-ordered/configs:/mounted -e PARAMS="-config /mounted/config.json" orderserver:build-with-alpine

docker : sh: /orderserver: not found

镜像因为不明原因报了找不到 orderserver 文件

2.3 使用 alpine 作为基础镜像构建, ENTRYPOINT 直接执行目标二进制文件,有更详细的报错信息,但仍然难以排查原因

2.3.1 编写 Dockerfile,ENTRYPOINT 直接执行目标程序
FROM alpine:latest
WORKDIR /
ENV PARAMS=""
ADD orderserver /
ADD configs /configs
EXPOSE 60001
#ENTRYPOINT ["sh", "-c", "/orderserver $PARAMS"]
ENTRYPOINT ["/orderserver"]
2.3.2 build

docker build -t orderserver:for-test .

Sending build context to Docker daemon  23.83MB

Step 1/7 : FROM alpine:latest
 ---> 961769676411
Step 2/7 : WORKDIR /
 ---> Running in 94a464e58a2a
Removing intermediate container 94a464e58a2a
 ---> a2bd2111ff1a
Step 3/7 : ENV PARAMS=""
 ---> Running in d836703cad25
Removing intermediate container d836703cad25
 ---> 1beadaa8423a
Step 4/7 : ADD orderserver /
 ---> a10094c7eebb
Step 5/7 : ADD configs /configs
 ---> 6e80ce8fb8cd
Step 6/7 : EXPOSE 60001
 ---> Running in c70a05d12a5f
Removing intermediate container c70a05d12a5f
 ---> c9d7ea756259
Step 7/7 : ENTRYPOINT ["/orderserver"]
 ---> Running in 430fd5268cf0
Removing intermediate container 430fd5268cf0
 ---> 9bb02c756cd0
Successfully built 9bb02c756cd0
Successfully tagged orderserver:for-test
2.3.3 运行镜像

报错信息:
docker : standard_init_linux.go:211: exec user process caused "no such file or directory"

与使用 sh 执行相比,报错信息更详细,不过貌似对问题排查仍然没什么帮助

2.4 使用 frolvlad/alpine-glibc 作为基础镜像构建并运行

2.4.1 编写 Dockerfile ,基础镜像 alpine-glibc
FROM frolvlad/alpine-glibc:latest
WORKDIR /
ENV PARAMS=""
ADD orderserver /
ADD configs /configs
EXPOSE 60001
ENTRYPOINT ["sh", "-c", "/orderserver $PARAMS"]
2.4.2 构建镜像

docker build -t orderserver:using-glibc .

Sending build context to Docker daemon  23.83MB

Step 1/7 : FROM frolvlad/alpine-glibc:latest
 ---> e8076a77c225
Step 2/7 : WORKDIR /
 ---> Running in fc67e5d9e5fa
Removing intermediate container fc67e5d9e5fa
 ---> 2ced8d6cdaf2
Step 3/7 : ENV PARAMS=""
 ---> Running in fbb048b38832
Removing intermediate container fbb048b38832
 ---> bec33277d8a1
Step 4/7 : ADD orderserver /
 ---> 7df727ecfc51
Step 5/7 : ADD configs /configs
 ---> 5dc592e179c3
Step 6/7 : EXPOSE 60001
 ---> Running in 001fa109df4e
Removing intermediate container 001fa109df4e
 ---> e1a8e1f7a580
Step 7/7 : ENTRYPOINT ["sh", "-c", "/orderserver $PARAMS"]
 ---> Running in 06fc223499e5
Removing intermediate container 06fc223499e5
 ---> 0c819ac833c6
Successfully built 0c819ac833c6
Successfully tagged orderserver:using-glibc
2.4.3 启动镜像

docker run -P --rm --name orderserver0 -v /docker/data/have-you-ordered/configs:/mounted -e PARAMS="-config /mounted/config.json" orderserver:using-glibc

看到一下信息代表Web应用能够正常启动了,底下报错只是因为没有配置Elasticsearch

2019/10/11 09:27:48 {{[http://es.0:49204]  } :60001 having-meal 1h}
os.Args: [/orderserver -config /mounted/config.json]
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET    /api                      --> have-you-ordered/internal/app/orderserver.ApiHelloGo (3 handlers)
[GIN-debug] GET    /api/ordered/:date        --> have-you-ordered/internal/app/orderserver.ApiOrdered (3 handlers)
[GIN-debug] POST   /api/order                --> have-you-ordered/internal/app/orderserver.PostOrder (3 handlers)
[GIN-debug] POST   /api/fake-order           --> have-you-ordered/cmd/orderserver/app.Run.func1 (3 handlers)
[GIN-debug] GET    /api/dashboard/agg-by-day --> have-you-ordered/internal/app/orderserver.AggHistogram (3 handlers)
[GIN-debug] POST   /api/control/fetch        --> have-you-ordered/internal/app/orderserver.ManuallyFetch (3 handlers)
[GIN-debug] Listening and serving HTTP on :60001
2019/10/11 09:27:48 Start Fetch Interval: 3600 sec
2019/10/11 09:27:48 Error getting response: dial tcp 127.0.0.1:49204: connect: connection refused
2019/10/11 09:27:48 Fetch interval error: runtime error: invalid memory address or nil pointer dereference

3 总结

  1. 选择基础镜像需要充分考虑运行环境要求,当遇到未知错误时,需要考虑运行环境兼容性问题。

其实说了这么多,本人水平有限,还是没有找到特别明确的原因,望指点🙏

<think>好的,用户问的是“docker创建一个容器可以使用多个镜像吗”,我需要先理解这个问题。通常Docker容器是基于一个镜像启动的,但用户可能想知道是否可以结合多个镜像来创建一个容器,或者在一个容器里使用多个镜像中的组件。 首先,Docker的基础知识是每个容器都从一个镜像启动,镜像是分层的,但一个容器只能直接使用一个基础镜像。用户可能误解了镜像和容器之间的关系,或者有特殊需求需要整合多个镜像的功能。比如,用户可能希望在一个容器里同时运行多个服务,这些服务原本分散在不同的镜像里,比如同时运行Nginx和MySQL,但通常Docker推荐每个容器只运行一个进程,所以这时候可能需要用Docker Compose来管理多个容器。 接下来,我需要思考用户为什么会问这个问题。可能的场景是用户希望简化部署,减少容器数量,或者不了解Docker的最佳实践。也有可能用户看到某些复杂应用需要多个组件,误以为需要在一个容器里使用多个镜像。 然后,我需要解释为什么Docker不支持一个容器多个镜像镜像是只读的,容器在镜像的基础上添加可写层。虽然镜像可以继承其他镜像(通过FROM指令),但运行时只能基于一个镜像启动。如果用户需要多个服务,应该使用多个容器,并通过网络连接它们,比如使用Docker Compose编排。 另外,用户可能有其他需求,比如在构建镜像时合并多个镜像的功能。这时候可能需要多阶段构建,或者在Dockerfile中复制其他镜像的文件。例如,使用COPY --from指令从其他镜像中复制文件到当前镜像,但这并不是在运行时使用多个镜像。 还要考虑用户可能的误区,比如认为多个镜像可以叠加运行环境。比如一个镜像装Python,另一个装Node.js,想要同时使用。正确的做法是创建一个新的镜像,基于一个基础镜像,然后安装所有需要的软件,而不是试图合并多个镜像到容器中。 最后,总结回答时要明确说明Docker不支持一个容器多个镜像,但提供替代方案,比如多容器编排、自定义镜像构建等,并解释每个方案的适用场景,帮助用户根据实际需求选择正确的方法。</think>### 核心结论 **Docker 容器不能直接使用多个镜像创建**,但可通过以下方案间接实现多镜像功能: --- ### 技术原理 1. **容器与镜像关系** - 每个容器基于**单个镜像**启动(镜像提供文件系统快照) - 容器运行时添加**可写层**(Copy-on-Write 机制) - 镜像之间可通过 `FROM` 继承但运行时仅加载最终合并层 2. **关键限制** $$ \text{单容器} \xrightarrow{\text{1:1映射}} \text{单镜像} $$ --- ### 替代方案 #### 方案 1:多阶段构建(开发推荐) 通过 `Dockerfile` 合并多个镜像资源: ```dockerfile # 第一阶段:使用 Golang 镜像编译 FROM golang:1.19 AS builder WORKDIR /app COPY . . RUN go build -o myapp # 第二阶段:使用 Alpine 镜像运行 FROM alpine:3.16 COPY --from=builder /app/myapp /usr/local/bin/ CMD ["myapp"] ``` ✅ 优势:最终镜像仅含运行所需文件(体积缩小 80%+) #### 方案 2:Sidecar 模式(生产推荐) 使用 Docker Compose 启动关联容器: ```yaml version: '3' services: web: image: nginx:1.23 volumes: - "./html:/usr/share/nginx/html" ports: - "80:80" monitor: image: prom/prometheus:latest ports: - "9090:9090" ``` ✅ 优势:各组件独立更新、隔离故障 #### 方案 3:超级镜像构建 合并多环境到单个镜像(慎用): ```dockerfile FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ python3 \ nodejs \ openjdk-17-jdk ``` ⚠️ 警告:镜像体积会显著膨胀(典型案例:基础 80MB → 混合 1.2GB) --- ### 关键数据对比 | 方案 | 镜像数量 | 隔离性 | 启动速度 | 典型使用场景 | |--------------------|--------|-------|--------|----------------| | 多阶段构建 | 1 | 高 | 快 | 构建-运行分离 | | Sidecar 模式 | N | 最高 | 中 | 微服务架构 | | 超级镜像 | 1 | 低 | 慢 | 本地开发测试 | --- ### 操作风险提示 1. **超级镜像缺陷** - 安全漏洞影响面扩大(如:Java + Python 同时存在漏洞) - 依赖冲突概率提升 73%(Ubuntu 官方镜像问题报告数据) 2. **正确做法参考** $$ \text{单一进程原则} = \frac{\text{1个容器}}{\text{1个主进程}} $$ --- ### 调试技巧 当需要验证多组件共存时: ```bash # 临时进入容器检查环境 docker run -it --rm composite-image bash # 检查已安装软件 which python3 && which node && java -version ``` --- ### 最终建议 - 开发环境:使用多阶段构建(方案1) - 生产环境:采用 Sidecar 模式(方案2) - 特殊场景:Kubernetes Init Containers 实现初始化依赖
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuweijie@apache.org

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值