Definition文件¶
一个singularity definition(def)文件是一个蓝本描述怎么build一个容器,和Dockerfile类似。 这个文件指定了基础容器,以及要在基础容器上安装的软件,要设置的环境变量,要从host上加到容器中的文件,和容器的metadata。
概述¶
一个singlarity definition文件可以分为两部分:
Header: Header部分描述了容器依赖的基础容器。
Sections: 文件剩余部分包含多个sections(有时也叫做scriptlets或者blobs)。 每个section以%开始,后面跟上这个section的名字,section是可选的。 当build容器的时候, 使用
/bin/sh
执行每个section。当容器运行的时候,runscript默认的也是/bin/sh
,可以接受/bin/sh
的参数。
更过def文件的例子,请看这里。 关于Dockerfile和Singularity definition文件的比较, 请参考这里。
Header¶
header在def文件的顶部,header描述容器依赖的基础容器,它包含一些关键字。
Bootstrap
关键字是必须的,它描述了你要使用哪种 bootstrap agent 获取基础容器。
比如如果Bootstrap是 library
,那么将从 Container Library 获取基础容器。
同样的如果Bootstrap是 docker
,那么将从 Docker Hub 获取基础容器。
从Singularity 3.2开始, Bootstrap
关键字必须出现在header的第一行,而以前的版本的则可以在header的任意位置。
根据 Bootstrap
的不同, header部分接下来的内容将不相同。
比如,当Bootstrap是 library
时,关键字 From
是有效的。
下面是从Container Library来获取debian基础容器:
Bootstrap: library
From: debian:7
下面使用OS官方源安装CentOS-7:
Bootstrap: yum
OSVersion: 7
MirrorURL: http://mirror.centos.org/centos-%{OSVERSION}/%{OSVERSION}/os/$basearch/
Include: yum
每种不同的bootstrap agent都有自己的关键字和选项。你可以查看 appendix section:
常用的bootstrap agents¶
其它bootstrap agents¶
localimage (容器在本机 saved on your machine)
yum (用于基于yum的CentOS和Scientific Linux)
debootstrap (用于基于apt的Debian和Ubuntu)
oci (OCI容器)
oci-archive (OCI容器打包文件)
docker-daemon (容器在本机的docker daemon)
docker-archive (容器是从docker导出的打包文件)
arch (Arch Linux)
busybox (BusyBox)
zypper (用于基于zypper的Suse和OpenSuse)
Sections¶
definition文件包含多个sections,不同的section添加不同的内容到容器或者在build容器的时候执行命令。 在build容器过程中,任何一条命令执行失败,build过程将停止。
下面是一个例子,其用到了所有的section类型,后面我们将一个个讨论这些不同的section。 当然一个def文件并不需要每种类型的section都包括。 另外,如果def文件中有多个同名的section,在build过程中后面section的内容会追加到前面的section中。
Bootstrap: library
From: ubuntu:18.04
Stage: build
%setup
touch /file1
touch ${SINGULARITY_ROOTFS}/file2
%files
/file1
/file1 /opt
%environment
export LISTEN_PORT=12345
export LC_ALL=C
%post
apt-get update && apt-get install -y netcat
NOW=`date`
echo "export NOW=\"${NOW}\"" >> $SINGULARITY_ENVIRONMENT
%runscript
echo "Container was created $NOW"
echo "Arguments received: $*"
exec echo "$@"
%startscript
nc -lp $LISTEN_PORT
%test
grep -q NAME=\"Ubuntu\" /etc/os-release
if [ $? -eq 0 ]; then
echo "Container base is Ubuntu as expected."
else
echo "Container base is not Ubuntu."
fi
%labels
Author d@sylabs.io
Version v0.0.1
%help
This is a demo container used to illustrate a def file that uses all
supported sections.
Section在def文件中的位置不重要。
%setup¶
build容器过程中,在 %setup
section的命令会首先执行,并且这些命令是在host上执行,而不是容器内部执行。
你可以通过环境变量``$SINGULARITY_ROOTFS``获取容器的文件系统。
Note
在build容器的时候,由于 %setup
section下的命令是在容器外执行,因此要小心使用,不要对host造成破坏。
上面的例子中:
%setup
touch /file1
touch ${SINGULARITY_ROOTFS}/file2
在 host 根目录下创建 file1
,这个文件我们在 %files
section会用到。在 container 内的根目录下创建 file2
。
后面的 %files
section提供了安全的方法拷贝host上的文件到container中,
由于使用 %setup
有可能会对host造成破坏,因此 %setup
不建议使用。
%files¶
%files
section 允许你拷贝host上的文件到容器中:
%files [from <stage>]
<source> [<destination>]
...
每行是一个 <source>
和 <destination>
对。 <source>
可以是:
host上的一个路径
前面阶段已经build出的文件的路径
<destination>
是container内的路径。如果没有提供 <destination>
,那么 <destination>
和 <source>
内容一样。
%files
/file1
/file1 /opt
第一行将拷贝host的根目录下的 file1
到container中的根目录下。
第一行将拷贝host的根目录下的 file1
到container中的/opt目录下。
definition文件中其它阶段生成的文件也可以拷贝到当前阶段中使用。
%files from stage_name
/root/hello /bin/hello
不同的时,host文件拷贝到container中,没有生成软链接,而其它阶段的文件拷贝到当前阶段,实际上是生成一个软链接。
%files
会在 %post
之前执行。
%app*¶
某些情况下,如果对每个app都build一个容器,由于这些app的依赖基本都相同,因此这些容器之间是冗余的。 因此基于 Standard Container Integration Format (SCI-F), singularity支持在一个容器中安装多个apps,更多关于app的信息,请查看 这里。
%post¶
在这一section,你可以从网络下载一些工具,像 git
和 wget
, 你可以使用这些工具下载软件并安装这些软件,当然你也可以创建和修改文件等。
%post
apt-get update && apt-get install -y netcat
NOW=`date`
echo "export NOW=\"${NOW}\"" >> $SINGULARITY_ENVIRONMENT
%post
使用Ubuntu包管理器 apt
更新系统和安装软件 netcat
( %startscript
将会用到这个软件)。
你不能在 %environment
中设置$SINGULARITY_ENVIRONMENT的值,将文本写入$SINGULARITY_ENVIRONMENT,
最终实际上会写入容器中的文件 /.singularity.d/env/91-environment.sh
,然后在运行的时候自动source这个文件。
%test¶
在build容器时候,最后会运行 %test
下面的脚本来测试进行容器,容器build好以后,你可以使用 test
命令运行 %test
下面的脚本。
%test
grep -q NAME=\"Ubuntu\" /etc/os-release
if [ $? -eq 0 ]; then
echo "Container base is Ubuntu as expected."
else
echo "Container base is not Ubuntu."
fi
这段%test用来check基础OS是否为Ubuntu。
如果在build的时候不希望运行%test,可以使用 --notest
的选项。
容器build好以后,可以使用test命令运行%test下的脚本。
$ sudo singularity build --notest my_container.sif my_container.def
$ singularity test my_container.sif
Container base is Ubuntu as expected.
%environment¶
%environment
可以设置容器运行时的环境变量。
需要注意的是,这里定义的环境变量在build阶段不能使用。
在build阶段如果要使用环境变量,需要在 %post
里面定义和使用:
build阶段:
%environment
下面的环境变量会被写到容器的metadata文件夹下的一个文件中,build的时候这个文件不会被source使用。运行阶段: metadata文件夹下的文件被source,可以使用定义的环境变量。
%environment
export LISTEN_PORT=12345
export LC_ALL=C
%startscript
将会用到 $LISTEN_PORT
环境变量,
$LC_ALL
会被很多程序(通常是perl程序)用到。容器build好以后,你可以验证环境变量是否生效。
$ singularity exec my_container.sif env | grep -E 'LISTEN_PORT|LC_ALL'
LISTEN_PORT=12345
LC_ALL=C
你也可以在 %post
里面设置环境变量。
build容器的时候, %environment
下的内容写到容器中的文件 /.singularity.d/env/90-environment.sh
中。
%post
中如果通过命令将内容写入 $SINGULARITY_ENVIRONMENT
,
那么这些内容实际上是写入了文件 /.singularity.d/env/91-environment.sh
中。
运行容器的时候, 在 /.singularity.d/env
下面的文件会按照顺序被source。
91-environment.sh会在90-environment.sh后被source,
因此这个例子中 %post
中写入的变量会优先于 %environment
中设置的变量。
关于singularity环境变量和metadata,请参考 Environment and Metadata。
%startscript¶
%startscript
下的内容在build的时候会写入容器中的一个文件,在运行 instance start
命令的时候会执行这个文件。
%startscript
nc -lp $LISTEN_PORT
这里netcat程序侦听TCP端口,这个端口是在 %environment
中设置的。
$ singularity instance start my_container.sif instance1
INFO: instance started successfully
$ lsof | grep LISTEN
nc 19061 vagrant 3u IPv4 107409 0t0 TCP *:12345 (LISTEN)
$ singularity instance stop instance1
Stopping instance1 instance of /home/vagrant/my_container.sif (PID=19035)
%runscript¶
%runscript
下的内容在build的时候会写入容器中的一个文件,
在运行 singularity run
命令或者直接运行容器的时候会执行这个文件。
当容器运行的时候,容器后面跟的参数会传给这个文件,这意味着在这个文件中你可以处理传递过来的参数。
%runscript
echo "Container was created $NOW"
echo "Arguments received: $*"
exec echo "$@"
上述运行脚本中,首先会打出容器创建的时间 $NOW
(这个变量是在 %post
中设置的)。
接着将传递给这个容器的所有参数作为一个字符串打印出来,然后讲所有参数以字符串数组方式传递给echo。
exec
用来生成一个新的echo进程来代替进入容器时的进程(shell进程),shell进程会结束,echo进程在容器中运行。
$ ./my_container.sif
Container was created Thu Dec 6 20:01:56 UTC 2018
Arguments received:
$ ./my_container.sif this that and the other
Container was created Thu Dec 6 20:01:56 UTC 2018
Arguments received: this that and the other
this that and the other
%labels¶
The %labels
用来添加metadata到容器文件 /.singularity.d/labels.json
中。
其格式是”名字 值”。
%labels
Author d@sylabs.io
Version v0.0.1
MyLabel Hello World
添加label,只需要在lables下面每行的第一个空格后面加上”名字 值”就可以。
上面例子中, 第一个label是 Author
,其值为 d@sylabs.io
。
第二个label是 Version
,其值为 v0.0.1
。
最后一个label是 MyLabel
,其值为 Hello World
。
你可以用inspect命令来查看容器的所有label。
$ singularity inspect my_container.sif
{
"Author": "d@sylabs.io",
"Version": "v0.0.1",
"MyLabel": "Hello World",
"org.label-schema.build-date": "Thursday_6_December_2018_20:1:56_UTC",
"org.label-schema.schema-version": "1.0",
"org.label-schema.usage": "/.singularity.d/runscript.help",
"org.label-schema.usage.singularity.deffile.bootstrap": "library",
"org.label-schema.usage.singularity.deffile.from": "ubuntu:18.04",
"org.label-schema.usage.singularity.runscript.help": "/.singularity.d/runscript.help",
"org.label-schema.usage.singularity.version": "3.0.1"
}
上面有些label是build的过程中自动生成的。关于label和metadata的更多信息,请参考 这里。
%help¶
%help
下的内容在build的时候会写入容器中的一个metadata文件。
run-help
命令能查看 %help
的内容。
%help
This is a demo container used to illustrate a def file that uses all
supported sections.
容器build好以后,使用下面命令查看%help的内容:
$ singularity run-help my_container.sif
This is a demo container used to illustrate a def file that uses all
supported sections.
多阶段Build¶
Singularity从3.2开始支持多阶段build, 比如一个阶段先在一个环境中编译出一个二进制文件,在最终阶段中可以拷贝这个二进制文件到最终容器,这样就减小了容器的大小。
Bootstrap: docker
From: golang:1.12.3-alpine3.9
Stage: devel
%post
# prep environment
export PATH="/go/bin:/usr/local/go/bin:$PATH"
export HOME="/root"
cd /root
# insert source code, could also be copied from host with %files
cat << EOF > hello.go
package main
import "fmt"
func main() {
fmt.Printf("Hello World!\n")
}
EOF
go build -o hello hello.go
# Install binary into final image
Bootstrap: library
From: alpine:3.9
Stage: final
# install binary from stage one
%files from devel
/root/hello /bin/hello
每个阶段的名字可以随意命名。每个阶段内的执行和单阶段执行相同。声明靠后的阶段可以拷贝前面的阶段的文件到当前阶段,
但是声明靠前的阶段不能拷贝声明靠后文件。因此 final
阶段能拷贝 devel
阶段的文件,而 devel
阶段不能拷贝 final
阶段的文件。
Apps¶
%app*
是相对独立的section。一个def文件可以有多个app,app之间没有顺序的关系。
The following runscript demonstrates how to build 2 different apps into the same container using SCI-F modules:
Bootstrap: docker
From: ubuntu
%environment
GLOBAL=variables
AVAILABLE="to all apps"
##############################
# foo
##############################
%apprun foo
exec echo "RUNNING FOO"
%applabels foo
BESTAPP FOO
%appinstall foo
touch foo.exec
%appenv foo
SOFTWARE=foo
export SOFTWARE
%apphelp foo
This is the help for foo.
%appfiles foo
foo.txt
##############################
# bar
##############################
%apphelp bar
This is the help for bar.
%applabels bar
BESTAPP BAR
%appinstall bar
touch bar.exec
%appenv bar
SOFTWARE=bar
export SOFTWARE
对一个app来说,%appinstall
和 %post
等价, %appenv
和 %environment
等价。
使用app:
% singularity run --app foo my_container.sif
RUNNING FOO
两个app中都定义了 $SOFTWARE
。你可以执行下面的命令查看当前app能用的环境变量。
$ singularity exec --app foo my_container.sif env | grep SOFTWARE
SOFTWARE=foo
$ singularity exec --app bar my_container.sif env | grep SOFTWARE
SOFTWARE=bar
build容器的一些建议¶
在容器中安装程序和创建文件的时候,这些程序和文件放在系统目录下,不要放在
/home
,/tmp
等有可能被mount覆盖的目录。添加容器的描述和说明,如果你的runscript不能提供帮助说明,在
%help
或者%apphelp
写上帮助说明, 一个好的容器应该告诉用户怎么使用它。如果你需要添加环境变量,添加到
%environment
和%appenv
中。在容器中创建的文件,其所有者应该是系统中用户id小于500的用户。
确保一些敏感文件像
/etc/passwd
,/etc/group
和/etc/shadow
不包含密码。用def文件Build容器,不要使用sandbox这种黑盒子的方式。