使用 Alpine 作为基础镜像

apline slogan

📜 目录

  • 👋 背景
  • 🚧 要求
  • 🌏 基础镜像选择
  • 💢 稳定性的问题
  • 💢 兼容性问题
  • 🥰 参考

👋 背景

在基于 Tekton 或者 Jenkins 作为构建引擎的 DevOps 产品中,总是需要内置一些工具类的镜像,这里面会包含我们需要的一些必要的二进制,比如 Git Ansible Helm yq jq 或许还有一些内部的 cli 工具,通常我们会将其命名为 tools 镜像,这个镜像会被应用在不同的 Task 中实现不同的功能。

随着时间的推移,无论什么地方的 Task 需要一个什么二进制,都会塞到这个镜像中来,久而久之这个镜像就变成了包含巨多工具超大难以维护的镜像,最主要的的问题就是回归工作量的问题

比如修改了镜像中的某一个制品版本,重新构建了 tools 镜像,为了保证功能正常,所有使用了该 tools 镜像的 Task 都需要回归测试,随着时间的推移,回归工作量就会越来越大。另外业界和客户对镜像安全也越来越重视,因此镜像经常性的需要升级工具版本来解决漏洞问题,也导致这个测试工作量问题越来越严重。

为了解决这个问题,可以考虑为不同 Task 配置专门的镜像,将这些 cli 工具拆分到对应的 Task 的小镜像中,这样镜像做了改动后,回归测试的范围就会足够小,漏洞也可以随时快速的修复。

🚧 要求

在为 Task 做专门的镜像时,会有如下几个需求

  • 镜像体积尽可能的小
  • 镜像包含的漏洞尽可能的少
  • 支持多架构 (amd64 与 arm64)
  • 稳定,确保每次构建出来的制品都是一样的
  • 兼容性,兼容之前的 Task 中的功能

🌏 选择基础镜像

目前可以选择的基础镜像大概有如下几个,基本信息如下

BaseImage Size Package Management Tool Architectures Vulnerability
Ubuntu 71M apt amd64/arm64 No
Apline 7.8M apk amd64/arm64 No
Distroless 2.6M No amd64/arm64 No

从体积上来看,Ubuntu 已经被抛弃了,从包管理工具来看,Distroless 也直接抛弃了,所以直接选择了适中的 Apline 作为基础镜像。

根据这次问题处理经验来看,Apline 的包管理工具 apk 要比 Ubuntu 的 apt 方便很多,很多工具比如 Ansible 使用 apk add 就可以直接安装好,但是 apt 就不行,还需要通过脚本判断架构然后再下载对应的二进制包到镜像中,麻烦也不容易维护。

因为有专门的团队维护基础镜像,所以也不需要担心漏洞和多架构的问题。

💢 稳定性的问题

为了保证镜像构建的稳定性,在通过 apk add 安装二进制工具时,需要指定安装工具的版本,防止每次构建都安装最新版本导致的兼容问题。一般来说,这种情况是很少见的,但是譬如 yqhelm 这种的工具,就会存在问题。

因为直接使用 apk add 的话都会安装最新版本,但是 yqhelm 不同版本却有很大的不兼容的改动,比如 yq3yq4 语法完全不兼容,helm2 和 helm3 命令也完全不同,不指定版本号安装可能就会带来兼容性的问题,导致升级前可以成功运行的 Task 在升级后无法正常运行。

apk add 安装工具时,可以通过该语法指定版本号 apk add bash==4.3.33-r0

通过这种方式指定后,就相当于安装了一个定时炸弹,因为 Alpine 社区各版本仓库只会保留最新版本的工具,其他的版本都会被删除,可以参考下四年前的相关议题 Alpine repo drops packages. This prevents package version pinning, and makes apk non-deterministic

因此如果你指定版本安装的工具有了新版本后,之前指定的版本就会被删除,安装命令就会失败导致镜像构建失败,到时候还要苦哈哈的修改版本号重新构建。

为了解决这个问题,一般会有这么几种办法

使用 Stable 版本的库

Stabel 或者已经归档的工具仓库中的工具版本不会再有更新,所以也就避免了上面提到的这个问题。但是这种旧版本的工具可能会存在漏洞,所以需要格外注意这一点

使用这种方式的话,需要指定 Stable 的仓库地址,确保 apk 可以找到旧版本工具,另外还需要获取 stable 版本工具的确切版本号,一般需要如下操作

  • 写入 Stable 仓库地址
    在 Dockerfile 执行 apk add 命令前,先将旧版本仓库地址写入到 /etc/apk/repositories 文件中,这样 apk 就可以找到旧版本的工具
    RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.2/main' >>   /etc/apk/repositories
    
  • 确定工具版本号
    通过 package filter,选择相关的版本与包,就可以获取到相关包的版本信息了,在 apk add 时指定旧的版本号就可以实现下载
    package filter

指定版本号范围

apk add 安装工具的时候,支持指定安装工具的版本范围,比如 Holding a specific package back 提到的 apk add 'asterisk>1.6.1' 就会下载高于 1.6.1 版本的 asterisk

目前支持的操作符可以参考这个 commit ,官方文档和 apk help 命令没有提供太多的说明

supported operators

在这个能力的加持下,我们可以指定一下工具的版本号范围,比如指定 RUN apk add --no-cache "ansible<=9.6",可以保证不会出现大版本改动导致兼容性的问题。

过一段时间,工具最新版本肯定会超过指定的版本范围,导致报错,但是频率也会比指定版本号要小一些。另外报错之后,我们也可以借这个机会看下是否工具有了不兼容改动,也可以适当的提升下版本号,扩展工具的能力。

本地安装

这个方式比较麻烦,但是一劳永逸,不会存在可能会出现的构建失败的问题

apk add 支持直接安装本地包,我们可以将我们需要的包下载到 Nexus 仓库中,然后在构建镜像的时候,将包下载下来,再通过 apk add 进行安装

使用方式参考 Add a local Package,首先需要下载你要安装的 apk 包到指定目录,随后通过如下命令安装
apk add --allow-untrusted /path/to/file.apk

如果你安装的 apk 存在其他的 apk 包的依赖,还需要指定所有的依赖包,例如
apk add --allow-untrusted /var/tig-2.2-r0.apk /var/git-2.11.1-20.apk

以上三种方式可以根据具体实际情况进行选择

💢 兼容性的问题

之前的 tools 镜像,使用的基础镜像是 Ubuntu 镜像,里面会内置很多的二进制工具,Alpine 中也会内置一些二进制工具,但是这些工具就是依赖 BuxyBox 提供的能力,这些二进制工具算是 阉割版本的二进制工具,会被阉割掉一些执行参数和相关能力。

BusyBox combines tiny versions of many common UNIX utilities into a single small executable. 

It provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. 

The utilities in BusyBox generally have fewer options than their full-featured GNU cousins

比如以下就是 Ubuntu 和 Apline 中 tar 命令支持的参数的差异

Apline 中的 tar 二进制支持的参数

BusyBox v1.36.1 (2024-06-10 07:11:47 UTC) multi-call binary.

Usage: tar c|x|t [-ZzJjahmvokO] [-f TARFILE] [-C DIR] [-T FILE] [-X FILE] [LONGOPT]... [FILE]...

Create, extract, or list files from a tar file

    c   Create
    x   Extract
    t   List
    -f FILE Name of TARFILE ('-' for stdin/out)
    -C DIR  Change to DIR before operation
    -v  Verbose
    -O  Extract to stdout
    -m  Don't restore mtime
    -o  Don't restore user:group
    -k  Don't replace existing files
    -Z  (De)compress using compress
    -z  (De)compress using gzip
    -J  (De)compress using xz
    -j  (De)compress using bzip2
    --lzma  (De)compress using lzma
    -a  (De)compress based on extension
    -h  Follow symlinks
    -T FILE File with names to include
    -X FILE File with glob patterns to exclude
    --exclude PATTERN   Glob pattern to exclude
    --overwrite     Replace existing files
    --strip-components NUM  NUM of leading components to strip
    --no-recursion      Don't descend in directories
    --numeric-owner     Use numeric user:group
    --no-same-permissions   Don't restore access permissions

Ubuntu 中 tar 支持的参数(为了方便描述,省略了很多篇幅)

GNU 'tar' saves many files together into a single tape or disk archive, and can
restore individual files from the archive.

Examples:
  tar -cf archive.tar foo bar  # Create archive.tar from files foo and bar.
  tar -tvf archive.tar         # List all files in archive.tar verbosely.
  tar -xf archive.tar          # Extract all files from archive.tar.

 Main operation mode:
  -A, --catenate, --concatenate   append tar files to an archive
  -c, --create               create a new archive
      --delete               delete from the archive (not on mag tapes!)
  -d, --diff, --compare      find differences between archive and file system
  -r, --append               append files to the end of an archive
      --test-label           test the archive volume label and exit
  -t, --list                 list the contents of an archive
  -u, --update               only append files newer than copy in archive
  -x, --extract, --get       extract files from an archive

 Operation modifiers:

      --check-device         check device numbers when creating incremental
                             archives (default)
  -g, --listed-incremental=FILE   handle new GNU-format incremental backup
  -G, --incremental          handle old GNU-format incremental backup
      --hole-detection=TYPE  technique to detect holes
      --ignore-failed-read   do not exit with nonzero on unreadable files
      --level=NUMBER         dump level for created listed-incremental archive
      --no-check-device      do not check device numbers when creating
                             incremental archives
      --no-seek              archive is not seekable
  -n, --seek                 archive is seekable
      --occurrence[=NUMBER]  process only the NUMBERth occurrence of each file
                             in the archive; this option is valid only in
                             conjunction with one of the subcommands --delete,
                             --diff, --extract or --list and when a list of
                             files is given either on the command line or via
                             the -T option; NUMBER defaults to 1
      --sparse-version=MAJOR[.MINOR]
                             set version of the sparse format to use (implies
                             --sparse)
  -S, --sparse               handle sparse files efficiently

 Local file name selection:
      --add-file=FILE        add given FILE to the archive (useful if its name
                             starts with a dash)
  -C, --directory=DIR        change to directory DIR
      --exclude=PATTERN      exclude files, given as a PATTERN
      --exclude-backups      exclude backup and lock files
      --exclude-caches       exclude contents of directories containing
                             CACHEDIR.TAG, except for the tag file itself
      --exclude-caches-all   exclude directories containing CACHEDIR.TAG
      --exclude-caches-under exclude everything under directories containing
                             CACHEDIR.TAG
      --exclude-ignore=FILE  read exclude patterns for each directory from
                             FILE, if it exists
      --exclude-ignore-recursive=FILE
                             read exclude patterns for each directory and its
                             subdirectories from FILE, if it exists
      --exclude-tag=FILE     exclude contents of directories containing FILE,
                             except for FILE itself
      --exclude-tag-all=FILE exclude directories containing FILE
      --exclude-tag-under=FILE   exclude everything under directories
                             containing FILE
      --exclude-vcs          exclude version control system directories
      --exclude-vcs-ignores  read exclude patterns from the VCS ignore files
      --no-null              disable the effect of the previous --null option
      --no-recursion         avoid descending automatically in directories
      --no-unquote           do not unquote input file or member names
      --no-verbatim-files-from   -T treats file names starting with dash as
                             options (default)
      --null                 -T reads null-terminated names; implies
                             --verbatim-files-from
      --recursion            recurse into directories (default)
  -T, --files-from=FILE      get names to extract or create from FILE
      --unquote              unquote input file or member names (default)
      --verbatim-files-from  -T reads file names verbatim (no escape or option
                             handling)
  -X, --exclude-from=FILE    exclude patterns listed in FILE

 File name matching options (affect both exclude and include patterns):

      --anchored             patterns match file name start
      --ignore-case          ignore case
      --no-anchored          patterns match after any '/' (default for
                             exclusion)
      --no-ignore-case       case sensitive matching (default)
      --no-wildcards         verbatim string matching
      --no-wildcards-match-slash   wildcards do not match '/'
      --wildcards            use wildcards (default for exclusion)
      --wildcards-match-slash   wildcards match '/' (default for exclusion)
...

这种情况下,如果之前使用了被阉割的参数,在替换成 Apline 基础镜像之后,就会带来不兼容的问题,Task 就会执行失败。

为了解决这个问题,还需要自己通过 apk add 来重新安装下 tar 以获取完整的归档能力。

🥰 参考

Alpine Packager Filter
how-to-install-a-specific-package-version-in-alpine
Alpine、Busybox、Distroless 三分天下,谁才是容器基础镜像的真正王者?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342