1. 为什么是iSulad:OpenEuler原生轻量容器引擎的选型逻辑
我最早接触iSulad,是因为在一批ARM架构的边缘网关设备上需要跑容器化应用。当时手头只有几百MB的内存余量,装Docker一套下来,守护进程常驻内存就要吃掉一两百MB,再加上containerd、runc、dockerd的进程链路,对小内存设备来说有点奢侈。后来查了一圈才发现,OpenEuler其实默认就带了一个轻量级候选——iSulad,华为系开源出来的容器引擎,C++实现,专门为资源受限场景设计。这篇文章就把我在OpenEuler 20.03 SP3和22.03 LTS上部署iSulad的完整过程、踩坑记录和调优经验整理出来,供正在做边缘计算、嵌入式容器化、或者想在欧拉上省资源的团队参考。
先说结论:如果你跑容器的目标是"功能完整生态丰富",Docker依然是稳妥的;但如果你在意的是系统资源占用、启动速度、与OpenEuler的深度集成,iSulad是更值得试的选择。它原生支持OCI规范,镜像格式和Docker兼容,也就是说你从Docker Hub拉下来的镜像,iSulad照样能用。命令上虽然不能百分百等号,但基本的拉取、运行、停止、删除,你花十分钟熟悉一下就能上手。
iSulad的架构和Docker有本质区别。Docker跑起来是"dockerd + containerd + runc"三件套,iSulad只有一个守护进程isulad,底层直接调用libcontainer和OCI runtime。进程数少,意味着上下文切换少,内存占用低,同时也意味着你排查问题时链路更短,问题定位相对直接。
适合谁来读这篇文章:一类是在OpenEuler上做云原生基础设施的运维工程师,装了欧拉之后想试试系统原生的容器方案;另一类是搞边缘网关、嵌入式盒子、IoT设备开发的,对资源占用斤斤计较;还有一类是纯粹对容器引擎内部机制感兴趣的开发者,想看看Docker之外另一种runtime是怎么工作的。
2. 部署前的三个准备:系统版本、内核参数与软件源配置
2.1 确认你的OpenEuler版本和架构
iSulad的安装看起来只是dnf install一句命令的事,但版本不匹配会带来隐性问题。我分别在以下环境做过验证:
| 操作系统版本 | 架构 | iSulad版本 | 状态 |
|---|---|---|---|
| openEuler 20.03 SP3 LTS | x86_64 | 2.0.x | 正常 |
| openEuler 20.03 SP3 LTS | aarch64 | 2.0.x | 正常 |
| openEuler 22.03 LTS | x86_64 | 2.1.x | 正常 |
| openEuler 22.03 LTS | aarch64 | 2.1.x | 正常 |
如果你用的是旧版本比如20.03 LTS(不带SP小版本号)或者更早的版本,仓库里的iSulad版本较老,功能不完整,比如对CNI网络插件的支持偏弱。建议至少升到20.03 SP1以上,或者直接用22.03 LTS系列。
2.2 内核参数与cgroup设置
iSulad运行容器依赖cgroup v2或v1,和Docker一样的逻辑。OpenEuler 22.03默认开启了cgroup v2,而你如果是从20.03升上来的,可能还停留在cgroup v1。区别在于,iSulad在cgroup v2环境下对资源限制的支持更完善,比如对cpuset的精细分配,更推荐用v2。
检查当前系统用什么版本:
mount | grep cgroup如果看到的是cgroup2 on /sys/fs/cgroup类型,那就是v2;如果看到一堆cgroup on /sys/fs/cgroup/cpu等挂载点,那就是v1。
需要修改的话,在内核启动参数里加systemd.unified_cgroup_hierarchy=1,然后reboot。改完确认:
mount | grep -w cgroup2顺便说一个容易忽略的点:如果服务器上开了SELinux并且处于Enforcing状态,iSulad的容器启动可能会被拦。OpenEuler默认SELinux是permissive,不算太严,但生产环境如果强制启用,需要安装对应的selinux策略包。最简单的验证方式:启动容器失败时看/var/log/audit/audit.log里有没有avc: denied记录。有的话临时用setenforce 0排除SELinux因素,再决定是调策略还是调整部署方案。
2.3 软件源设置:欧洲拉源与EPOL仓库
iSulad的包在OpenEuler的**EPOL(Extra Packages for openEuler)**仓库里,不在默认的baseos里。所以安装前必须确保epol源是通的。
我踩过的一个坑是:装完最小化系统后直接dnf install iSulad,结果"No package iSulad available"。查了半天才发现,系统只启用了baseos和update仓库,epol没配。在干净系统上,你要做的操作是:
dnf install epel-release -y不对,那是CentOS的习惯。OpenEuler上正确的操作是安装epol-release这个包:
dnf install epol-release -y装完后检查:
dnf repolist | grep epol确认能看到类似openEuler 22.03-LTS EPOL的条目。如果提示找不到epol-release,说明你的软件源配置文件可能本身没指向欧拉仓库,需要检查/etc/yum.repos.d/openEuler.repo里的baseurl地址是否和你系统版本对应。
国内服务器建议把baseurl改成镜像站。我常用的有华为云镜像和清华镜像,以22.03为例,在openEuler.repo里将baseurl=http://repo.openeuler.org/...替换成:
baseurl=https://mirrors.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/main/$basearch/替换后执行:
dnf clean all dnf makecache等缓存生成完再安装。
2.4 预装依赖与版本锁定
iSulad安装时会自动拉取依赖,包括libisula、lxc、libcurl等。我建议在安装前先用dnf install把以下基础依赖暴露出来装好,避免安装过程中版本冲突:
dnf install -y tar libcurl-devel openssl-devel systemd-devel这两行不是必须的,但在某些最小化系统上,预装这些包能少踩不少坑。
3. 安装iSulad:两种方式与一条主线
3.1 标准方式:dnf直接安装
软件源配好之后,安装其实非常顺手:
dnf install -y iSulad安装完成后,查看版本:
isula version正常会输出Client和Server两个版本号,Server那一行如果出现了,说明守护进程已经可以正常连接了。如果没有Server信息,多半是isulad服务没启动。
启动并设置开机自启:
systemctl enable --now isulad systemctl status isulad这一步应该能看到active (running)。如果启动失败,用journalctl -u isulad看日志,比较常见的报错是/var/lib/isulad目录权限问题,或者和已安装的containerd/runc冲突。
3.2 编译安装:源码包的适用场景
如果你的架构比较特殊,比如RISC-V、MIPS或者某个定制化的ARM平台,仓库里没有预编译包,那就得走源码编译。iSulad的源码在Gitee上(openEuler仓库),编译依赖CMake、gcc-c++、protobuf、grpc这些工具链。
编译的关键步骤如下,我以x86_64的openEuler 22.03为例:
dnf install -y git cmake gcc-c++ protobuf-compiler protobuf-devel grpc-devel git clone https://gitee.com/openeuler/iSulad.git cd iSulad mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release .. make -j$(nproc) make install注意几点:
CMAKE_INSTALL_PREFIX建议指定为/usr,这样二进制文件会放到/usr/bin,systemd服务文件放/usr/lib/systemd/system,符合FHS惯例。如果不指定,默认装到/usr/local,那systemd要用绝对路径才能管理。- 编译时间取决于机器性能,x86_64上大概10到20分钟,ARM上可能更久。建议用
make -j$(nproc)跑满核数。 - 编译完成后,还需要手动安装systemd service文件,或者用make install自动处理。确认一下
/usr/lib/systemd/system/isulad.service存在,不存在就拷贝源码目录里的isulad.service过去。
3.3 版本选择的个人建议
从稳定性角度,我倾向于不用最新版本,除非你有明确的新特性需求。iSulad 2.0.x在20.03 SP3上表现很稳,2.1.x在22.03上引入了更好的CRI支持,但对于只跑普通容器的场景,没有本质差别。需要Kubernetes CRI场景的人,优先选2.1.x以上。
安装完成后,随手做一件事:
isula info这个命令类似Docker的info,能看到内核版本、存储驱动、cgroup驱动、镜像数量等。我遇到过一次Storage Driver显示为vfs的情况,这就是存储驱动没配好,后面第6节会展开说。
4. 配置核心参数:/etc/isulad/daemon.json的完整解读
iSulad的配置文件是/etc/isulad/daemon.json,安装完默认有一份。这个文件决定了守护进程的行为,花几分钟读懂它,后面能省很多排查时间。我把一份我实际用的配置贴出来,附上每一段的含义:
{ "group": "isulad", "default-runtime": "runc", "runtime": { "runc": { "path": "/usr/bin/runc" } }, "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn" ], "insecure-registries": [ "registry.local:5000" ], "storage-driver": "overlay2", "storage-opt": [ "overlay2.override_kernel_check=true" ], "exec-mount-flags": [ "noexec" ], "cgroup-parent": "", "log-driver": "stdout", "log-level": "INFO", "hook-spec": "/etc/isulad/hooks/default" }各项的作用逐个说:
group:isulad的socket访问组,默认isulad。用户想免sudo使用isula命令,把自己加入这个组就行:usermod -aG isulad yourname,然后重新登录。default-runtime:底层OCI runtime,默认runc。如果你想用crun、runsc这些,就在这里切换。registry-mirrors:镜像加速器。因为Docker Hub在国内访问不稳定,配一个镜像地址会舒服很多,尤其是拉取nginx、busybox这种常用镜像时体感明显。个人建议配两家,第一家挂了还能自动切换。注意,国内的一些老加速器地址已经失效,我目前实测可用的是中科大镜像和华为云的加速器(如果你的服务器在华为云上,加速器地址也可以填官方控制台里的那个)。insecure-registries:配置HTTP协议的私有仓库。自建Harbor如果用HTTP方式暴露,就必须在信任列表里加上,否则isulad拒绝推送和拉取。storage-driver:存储驱动。默认为overlay2,但由于欧拉内核版本影响,某些场景下会自动降级到vfs。vfs不支持写时复制,每个容器层都是完整拷贝,镜像占用空间成倍增长,速度也慢。建议显式配置overlay2,并加上overlay2.override_kernel_check=true跳过内核检查。log-level:日志级别,INFO足够。遇到诡异问题再临时调成DEBUG,跑一会儿再改回来。DEBUG日志非常啰嗦,不要长期开启。hook-spec:OCI hook配置路径。这是iSulad的一个特色,可以通过hook在容器生命周期特定节点执行脚本。默认配置文件里通常是空配置,但后面我们要用它解决一个很实际的设备映射问题。
配置改完后,重启守护进程:
systemctl restart isulad再验证:
isula info注意看一下Storage Driver是不是overlay2,不是就继续排查内核模块,重点检查/sys/module/overlay/parameters/下是否有内容。
5. 第一组容器实操:拉镜像、跑容器、进终端
5.1 拉取镜像与镜像管理
我用nginx作为演示镜像,因为体积适中,启动后行为明确:
isula pull nginx:alpine拉取速度取决于网络,用镜像加速器大概十几秒。如果拉取失败,报timeout或者TLS handshake timeout,大概率是加速器地址问题,换成另一家再试。
查看本地镜像列表:
isula images输出格式和docker images几乎一样,REPOSITORY、TAG、IMAGE ID、SIZE。SIZE显示的是镜像解压后的大小,不是压缩包的大小——这个和Docker一致,下载时看到的体积和这里的体积有差异属于正常现象。
删除镜像用:
isula rmi nginx:alpine注意:有容器在使用该镜像时,rmi会报冲突,需要先删除或停止相关容器。
5.2 运行第一个容器
isula run -d --name web -p 8080:80 nginx:alpine参数含义和Docker基本一致:
-d:后台运行--name:容器名-p:端口映射,宿主机8080映射到容器80
运行后执行isula ps,状态列显示Up。在宿主机上用curl 127.0.0.1:8080,如果返回nginx的欢迎页,说明端口映射和网络都正常。这一条通了,意味着iSulad的基本链路——镜像、存储、网络、运行时——都是通的。
如果想交互式运行容器,比如起一个Alpine的shell:
isula run -it alpine /bin/sh和Docker一样的操作。不过有个差异,iSulad默认不分配tty时,-t需要显式指定,否则某些交互命令会报"input device is not a TTY"。建议在跑交互式容器时,始终写成-it。
5.3 进入运行中的容器
在Docker里是docker exec -it <container> /bin/sh,iSulad用的是isula exec:
isula exec -it web /bin/shexec命令支持的子命令没有Docker多。比如--user参数,老版本2.0.x可能不支持指定容器内用户去执行命令,2.1.x才补上。如果你需要以非root用户执行命令,建议先确认版本再操作。
还有一点:iSulad 2.0.x里,isula exec不支持--env参数来注入临时环境变量。需要注入环境变量的话,要么在run时用-e,要么在容器里export。
5.4 日志查看
isula logs -f web这个命令的实现和Docker不完全一样。Docker是永远保存所有 stdout/stderr 历史日志的,iSulad则依赖日志驱动配置,如果log-driver设置的stdout,日志通过stdout输出并由systemd的journald捕获。优点是不额外占磁盘,但缺点是日志轮转策略依赖journald自己的限制。如果你的应用日志量大,建议给journald配额:
systemctl edit systemd-journald写入:
[Journal] SystemMaxUse=500M MaxRetentionSec=7d然后重启journald。否则日志占满/var/log/journal,最直观的后果是journal查询变慢,极端情况会拖累系统盘写入。
6. 存储驱动与镜像空间管理
6.1 overlay2为什么重要
容器镜像分层机制依赖写时复制。overlay2是主流方案,性能好、空间省。iSulad镜像和容器的数据默认放在/var/lib/isulad下,和Docker的/var/lib/docker结构类似,有三个关键子目录:
overlay2/:镜像层与容器读写层数据containers/:容器元数据image/:镜像元数据库
如果存储驱动是vfs,那你每运行一个容器,整个镜像都会完整复制一份到容器的读写层。nginx alpine镜像解压后约50MB,这不夸张,但你跑5个容器就是250MB,跑50个就是2.5GB,磁盘空间很快会被吃光。而overlay2方式,50个容器共享同一份镜像只读层,每个容器只占用少量读写层空间。
检查当前存储驱动:
isula info | grep "Storage Driver"使用overlay2的前提是内核支持overlayfs模块。OpenEuler默认内核一般没问题,执行modprobe overlay后检查:
lsmod | grep overlay如果内核模块加载失败,或者文件系统不支持d_type探测,isulad会拒绝使用overlay2并自动回退到vfs。回退并不报错,只会在info里显示Storage Driver: vfs,不仔细看根本发现不了。这也是我建议安装完成后马上执行isula info的原因。
6.2 镜像空间占用排查
/var/lib/isulad目录的膨胀速度比想象中快。排查占用用常规方法就行:
du -sh /var/lib/isulad/* | sort -rh我遇到过一次空间异常膨胀的情况,du显示overlay2目录占了几十GB,但isula images列表里的镜像加起来不过几个GB。最后定位是有一个容器在持续写日志,输出的数据全进了容器读写层,那个容器删除后空间就释放了。教训是:日志量大的应用一定要在容器层或应用层做日志轮转,别指望容器引擎帮你兜底。
清理空间用:
isula rmi $(isula images -q)注意这条命令会删除所有没有容器在使用的镜像,用之前先检查isula ps -a。另一种比较实用的是定期清理停止状态的容器:
isula rm $(isula ps -a -q)和Docker一样,正在运行的容器不能被rm,会报错。需要强制删除加-f。
6.3 磁盘满时的临时措施
真的遇到临时停电式磁盘写满,第一件事是:isula ps -a找出停止状态的容器,直接rm掉;如果还是不够,找出运行中的容器,确认是哪个容器在写大量数据,把容器的写入方向调整到挂载的卷里再做日志策略。iSulad本身没有docker system prune那么方便的空间回收命令,手工处理为主。
7. 网络模式:从bridge到host的真实选择
7.1 三种网络模式的区别
iSulad的网络模式相比Docker要简单一些,主要有三种:
| 模式 | 原理 | 适用场景 |
|---|---|---|
| bridge | 容器通过虚拟网桥上连,经NAT访问外网 | 单机多容器,需要端口映射的场景 |
| host | 容器直接使用宿主机网络栈 | 对网络性能要求极高,或者端口数量多的场景 |
| none | 只有loopback,无外部网络 | 只跑离线任务/安全隔离场景 |
默认模式是bridge。和Docker不同的是,iSulad的bridge网络默认不会有DNS轮转之类的功能,容器之间通信靠--link或者直接配置在同一个网桥下。
我看到过一个比较危险的用法:在host模式下运行容器,容器内的nginx直接绑定80端口,而宿主机上另一个服务也想用80端口,必然冲突。如果你必须在host模式跑多个容器,建议通过环境变量或应用配置让每个容器监听不同端口,或者干脆用bridge模式。
7.2 宿主机端口访问容器服务的配置方法
iSulad的端口映射语法和Docker一致:
isula run -d -p 宿主机IP:宿主机端口:容器端口 --name web nginx举例,只允许内网IP 192.168.1.10访问8080端口:
isula run -d -p 192.168.1.10:8080:80 --name web nginx这条映射规则和iptables是打通的,isulad启动容器时会自动写入NAT规则。
如果发现宿主机上访问不到容器端口,按这个顺序排查:
- 检查容器进程是否真正在监听:
isula exec web netstat -tlnp - 检查宿主机端口是否被占用:
ss -tlnp | grep 8080 - 检查iptables FORWARD链是否拦截:
iptables -L FORWARD -v,看到policy DROP就放行容器所在网段 - 检查SELinux,看audit.log
7.3 容器间通信配置
在一个宿主机上跑多个iSulad容器,让它们互相通信,最简单的方式是使用同一个自定义网络?遗憾的是,iSulad 2.0.x对自定义网络的创建支持很弱,没有isula network create这样完整的命令。实际操作中,我常用--link来建立容器间连接。
isula run -d --name db redis isula run -d --name app --link db:db alpine sleep 3600app容器里就能直接通过主机名db访问redis容器,这个机制本质是往app容器的/etc/hosts里写了db的IP,对网络性能几乎没有影响。
需要更复杂网络拓扑的话,建议用host模式共享网络栈,或者在宿主机上创建bridge并让iSulad容器挂到bridge上。
8. systemd管理容器与服务化运行
8.1 用systemd保持容器开机自启
生产环境跑容器,不能只靠isula run -d,因为宿主机重启后,容器不会自动恢复到running状态。iSulad不像Docker有restart: always这种现成的compose字段,得靠systemd兜底。
先创建一个systemd服务文件,比如/etc/systemd/system/nginx-container.service:
[Unit] Description=my nginx container After=isulad.service Requires=isulad.service [Service] ExecStartPre=-/usr/bin/isula rm -f web ExecStart=/usr/bin/isula run --name web -p 8080:80 nginx:alpine ExecStop=/usr/bin/isula rm -f web Restart=always RestartSec=10 [Install] WantedBy=multi-user.target几个关键点说明:
ExecStartPre里的-前缀表示忽略错误,即使容器不存在,命令报错也不会中断服务启动。ExecStart这里要注意:如果isula run带-d,systemd会认为服务启动完成后就退出,不会持续跟踪容器状态。上面的例子里没有加-d,isula run会以前台模式运行,systemd能看到进程存活。这是故意设计的。Restart=always配合RestartSec=10,容器挂了10秒后自动拉起;isulad服务如果重启,这个容器服务也会被拉起。
创建后执行:
systemctl daemon-reload systemctl enable --now nginx-container.service8.2 开机自启的另一种方式
如果你不想为每个容器写一个service文件,可以依赖isulad本身的--restart策略。在isula run时传--restart=always,守护进程会管理容器的重启。这样宿主机重启后,isulad服务优先启动,然后由isulad把标记了restart策略的容器拉起来。
两种方式选择上,我建议:容器数量少(低于十个)时用systemd,管理粒度更细,systemctl status一眼看到状态;容器数量多时,用isulad自身restart策略,省掉一堆service文件。不建议混合使用,否则同一个人为操作的自动化任务会出现两个触发源,容易查不清问题。
9. 与Docker共存的正确方式
9.1 端口监听冲突
在我实际部署中,经常出现的情况是:服务器上已经装了Docker,想再装iSulad做测试。两个引擎可以共存,但要注意两个冲突点。
第一个是端口监听冲突。isulad的守护进程和docker的守护进程各自监听各自的socket,一般不会撞。但Docker容器如果映射了8080端口,iSulad容器再映射8080就会被拒绝。
第二个是存储目录冲突。如果默认配置不改,两者数据目录独立(/var/lib/docker和/var/lib/isulad),不会互踩,但磁盘占用也是双份。
建议将iSulad数据目录独立分区挂载,避免同一块盘写满互相拖累。在daemon.json里修改:
{ "root": "/data/isulad" }修改后要重启isulad,并确认/data/isulad/overlay2正常。
9.2 镜像互通技巧
Docker和iSulad共享OCI镜像格式,意味着你可以从Docker里导出镜像再导入iSulad:
docker save nginx:alpine -o nginx.tar isula load -i nginx.tar反过来也支持:
isula save nginx:alpine -o nginx.tar docker load -i nginx.tar这个互通能力在迁移场景里很实用。我在内网环境就经历过:一台机器上有Docker和一堆镜像,另一台只有iSulad且没有外网。直接用save/load迁移镜像,省掉了重新构建的麻烦。
9.3 容器网络互通的落地方式
Docker和iSulad的容器要互相访问,网络栈不同,默认情况下跨引擎访问不通。一个变通方案是让两边的容器都用host网络模式,这样大家都用宿主机IP,端口区分服务,天然互通。
docker run -d --network host --name web1 nginx isula run -d --network host --name web2 nginx宿主机IP:8080 访问web1,宿主机IP:8081 访问web2,两边互不干扰。
如果你的容器服务分布在不同的网络模式中,想实现跨引擎互通,可以在宿主机层面做iptables DNAT规则或者用nginx反代转发,操作成本都不高。
10. 排错实录:我在这套方案里踩过的三个典型问题
10.1 容器启动失败:找不到runc
新装完iSulad后,第一次isula run报错failed to create container: runc did not terminate successfully。
排查过程:
which runc发现runc根本没安装。Docker自带runc,iSulad不自带,需要单独装。解决方案:
dnf install -y runc装完后重启isulad再试。这个问题不算复杂,但因为安装日志不会主动提示缺runc,只会在启动容器时才爆出来,容易被忽视。
10.2 镜像拉取超时:仓库地址失效
isula pull时卡很久然后报connection timed out。
我最初配的加速器地址是某老牌镜像站点,后来该站点停止服务了。改了中科大镜像后立刻恢复正常。这里分享一个判断方法:先curl -I https://docker.mirrors.ustc.edu.cn/v2/,如果在服务器上能返回HTTP 200或401(401也说明网络通),那就是通的。如果是curl超时,那源站确实不可达,换个镜像站。
国内生产环境用得多的镜像加速地址还有:华为云加速器(阿里云也有对应加速器,需要登录控制台获取专属地址)。内网环境建议直接用另一个可用节点上的registry-mirrors,或者用insecure-registries指向内网Harbor。
10.3 hook脚本导致的启动卡死
一个比较隐蔽的问题:容器启动后卡在isula run执行阶段,容器状态一直显示Created但不进入Running。
后来定位是hook脚本卡住了。iSulad的hook机制会在容器启动的特定阶段调用外部命令,如果命令执行时间过长,直接影响容器启动流程。查看hook配置:
cat /etc/isulad/hooks/default这个文件里配置的prestart、poststart这些钩子。卡住的原因是我写的一个prestart脚本里去访问了一个不存在的NFS挂载点,阻塞了十几秒。删掉那条hook后恢复正常。
hook这个功能本身是灵活的,不是坑。但要注意hook脚本的超时控制,脚本里加超时逻辑:
timeout 5 /path/to/your/script这样即使脚本卡住,也不会拖垮容器启动。
11. 性能数据:iSulad和Docker在同机上的对比
这一节放一组我在OpenEuler 22.03 LTS、x86_64、4核8G、nvme磁盘上实测的数据。测试方式是:冷启动同一个nginx:alpine镜像50次,取平均,并测量daemon常驻内存。
| 指标 | Docker | iSulad |
|---|---|---|
| 守护进程常驻内存 | 150-200MB | 40-60MB |
| 镜像冷启动到响应(平均) | 800ms | 500ms |
| 容器启动并发30个 | 稳定 | 稳定 |
| 内存占用(跑10个nginx容器) | 1.2GB | 900MB |
内存上的差距是最直观的,接近3倍的差异,在嵌入式设备上这个优势会放大。CPU占用方面,常规使用下两者差别不大,都是事件驱动模型,闲时CPU占用都低于1%。
iSulad对CNI插件的集成没有Docker那么成熟。如果你需要跑Kubernetes节点,iSulad走的是CRI接口而不是CNI模型,中间需要额外配置CRI网络工具。单机部署、边缘盒子的场景,iSulad完全够用;大规模K8s集群,还是Docker或containerd更顺手。
12. 挂载、权限与设备映射:边缘设备上最常用的功能
12.1 挂载宿主目录进容器
边缘设备上最常见的需求是让容器访问宿主的配置文件、日志目录或者数据盘。iSulad支持-v参数,用法和Docker一模一样:
isula run -d --name app -v /opt/app/config:/etc/app-config -v /mnt/data:/data nginx:alpine-v使用"宿主机目录:容器目录"的格式,还可以加:ro做成只读:
isula run -d --name app -v /opt/app/config:/etc/app-config:ro nginx:alpine配置文件做只读挂载是很好的习惯,防止容器内进程误改宿主文件。
如果挂载目录不存在,iSulad和Docker一样会自动创建宿主目录。这里有个细节:自动创建目录的权限取决于isulad进程的umask,通常是0755,如果你挂载的目录需要特定权限比如0777,最好手动先mkdir并chmod,不要依赖自动创建。
12.2 容器内设备权限控制
在容器里使用USB设备、串口、GPU这类设备时,需要把设备映射进容器。iSulad的用法是:
isula run -d --name gpu-app --device /dev/nvidia0:/dev/nvidia0 ubuntu sleep 3600格式是--device 宿主机设备路径:容器内设备路径。
还有一个--privileged参数,加上后容器内几乎拥有宿主机所有设备访问权限。这个参数慎用,安全边界直接消失。我的经验是:能明确用--device指定设备,就不要用--privileged;必须用privileged时,确保容器部署在可信网络环境且镜像来源可控。
12.3 用户权限映射的坑
容器内默认root用户不是宿主机root。容器内uid 0映射到宿主机是isulad的用户命名空间。想实现容器内uid和宿主uid一致,可以用--user参数:
isula run -d --user 1000:1000 --name app nginx:alpine这个参数2.1.x以上比较稳定,2.0.x下有时不生效,升级到2.1.x基本都能用。
13. 从零到一:一个嵌入式网关的完整部署案例
最后用一个实际案例把整篇内容串起来。我的一个客户项目,设备是ARM架构的边缘网关,系统是openEuler 20.03 SP3,4核A53,2GB内存,16GB eMMC,需要跑业务容器和数据采集容器。
整个部署过程如下:
- 系统准备:确认内核版本4.19以上,SELinux设为permissive。
- 安装epol-release并更新缓存。
- 安装iSulad、runc。
- 编辑daemon.json:配置overlay2存储驱动、中科大镜像加速器。
- 启动并验证isulad守护进程。
- 从内网Harbor拉取两个业务镜像(离线场景则用isula load导入)。
- 启动数据采集容器,用host网络模式,直接绑宿主机网口,方便透传Modbus和CAN数据。
- 启动业务容器,用bridge网络模式,映射8080端口对外提供Web管理页。
- 为两个容器各写一个systemd service,设置开机自启和崩溃重启。
- 用
isula logs和journalctl观察容器日志,打入到中央日志系统。
整套方案运行半年,最明显的变化是500MB的内存余量比原来Docker方案多出了近200MB,这些内存分给了业务应用做缓存,宿主机的负载明显轻了。
还有一个细节是,eMMC这类存储介质对反复擦写比较敏感。我在部署时特意把isulad的root目录指向了一个单独的存储分区,并且对日志量大的容器配置了只读根文件系统,防止容器运行时往根分区持续写数据,延长存储寿命。
如果你计划在类似的资源受限场景上跑容器,我的建议是:直接用iSulad而不是Docker;网络模式优先考虑host,减少NAT开销;对磁盘空间做分区规划,避免/var/lib/isulad和系统盘混在一起。合理编排下,这台2GB内存的网关设备跑8到10个容器没有问题。
14. 我个人的一些体会
用了iSulad之后,最大的感受是"容器引擎本可以不那么重"。Docker给我们的体验很好,市场占有率说明一切,但它的体积和依赖链在边缘场景里确实是一种负担。iSulad的定位更精准——保住OCI标准、保住镜像互操作性、保住Kubernetes接口,同时把资源占用压低。
要说的一个现实是,iSulad的社区活跃度和文档完整度比Docker差不少,踩坑大多数要靠自己翻源码和看日志。比如某个flag究竟支持哪些参数,不定式去源码里查,基本找不到准确文档。这也是为什么我在这篇文章里尽量把能遇见的坑都列出来。
最后分享一个小技巧:测试新版本iSulad时,先在一台和线上环境一致的测试机上完整跑一遍isula run、isula exec、isula load/save、systemctl restart isulad这四组操作,再决定要不要上生产。容器引擎这种基础组件,稳比新重要,不折腾就是最大的效率。