编译

编译环境

由于SONiC是基于debian开发的,为了保证我们无论在什么平台下都可以成功的编译SONiC,并且编译出来的程序能在对应的平台上运行,SONiC使用了容器化的编译环境 —— 它将所有的工具和依赖都安装在对应debian版本的docker容器中,然后将我们的代码挂载进容器,最后在容器内部进行编译工作,这样我们就可以很轻松的在任何平台上编译SONiC,而不用担心依赖不匹配的问题,比如有一些包在debian里的版本比ubuntu更高,这样就可能导致最后的程序在debian上运行的时候出现一些意外的错误。

初始化编译环境

安装Docker

为了支持容器化的编译环境,第一步,我们需要保证我们的机器上安装了docker。

Docker的安装方法可以参考官方文档,这里我们以Ubuntu为例,简单介绍一下安装方法。

首先,我们需要把docker的源和证书加入到apt的源列表中:

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

然后,我们就可以通过apt来快速安装啦:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

安装完docker的程序之后,我们还需要把当前的账户添加到docker的用户组中,然后退出并重新登录当前用户,这样我们就可以不用sudo来运行docker命令了!这一点非常重要,因为后续SONiC的build是不允许使用sudo的。

sudo gpasswd -a ${USER} docker

安装完成之后,别忘了通过以下命令来验证一下是否安装成功(注意,此处不需要sudo!):

docker run hello-world

安装其他依赖

sudo apt install -y python3-pip
pip3 install --user j2cli

拉取代码

3.1 代码仓库一章中,我们提到了SONiC的主仓库是sonic-buildimage。它也是我们目前为止唯一需要安装关注的repo。

因为这个仓库通过submodule的形式将其他所有和编译相关的仓库包含在内,我们通过git命令拉取代码时需要注意加上--recuse-submodules的选项:

git clone --recurse-submodules https://github.com/sonic-net/sonic-buildimage.git

如果在拉取代码的时候忘记拉取submodule,可以通过以下命令来补上:

git submodule update --init --recursive

当代码下载完毕之后,或者对于已有的repo,我们就可以通过以下命令来初始化编译环境了。这个命令更新当前所有的submodule到需要的版本,以帮助我们成功编译:

sudo modprobe overlay
make init

了解并设置你的目标平台

SONiC虽然支持非常多种不同的交换机,但是由于不同型号的交换机使用的ASIC不同,所使用的驱动和SDK也会不同。SONiC通过SAI来封装这些变化,为上层提供统一的配置接口,但是在编译的时候,我们需要正确的设置好,这样才能保证我们编译出来的SONiC可以在我们的目标平台上运行。

现在,SONiC主要支持如下几个平台:

  • barefoot
  • broadcom
  • marvell
  • mellanox
  • cavium
  • centec
  • nephos
  • innovium
  • vs

在确认好平台之后,我们就可以运行如下命令来配置我们的编译环境了:

make PLATFORM=<platform> configure
# e.g.: make PLATFORM=mellanox configure

Note

所有的make命令(除了make init)一开始都会检查并创建所有debian版本的docker builder:bullseye,stretch,jessie,buster。每个builder都需要几十分钟的时间才能创建完成,这对于我们平时开发而言实在完全没有必要,一般来说,我们只需要创建最新的版本即可(当前为bullseye,bookwarm暂时还没有支持),具体命令如下:

NOJESSIE=1 NOSTRETCH=1 NOBUSTER=1 make PLATFORM=<platform> configure

当然,为了以后开发更加方便,避免重复输入,我们可以将这个命令写入到~/.bashrc中,这样每次打开终端的时候,就会设置好这些环境变量了。

export NOJESSIE=1
export NOSTRETCH=1
export NOBUSTER=1

编译代码

编译全部代码

设置好平台之后,我们就可以开始编译代码了:

# The number of jobs can be the number of cores on your machine.
# Say, if you have 16 cores, then feel free to set it to 16 to speed up the build.
make SONIC_BUILD_JOBS=4 all

Note

当然,对于开发而言,我们可以把SONIC_BUILD_JOBS和上面其他变量一起也加入~/.bashrc中,减少我们的输入。

export SONIC_BUILD_JOBS=<number of cores>

编译子项目代码

我们从SONiC的Build Pipeline中就会发现,编译整个项目是非常耗时的,而绝大部分时候,我们的代码改动只会影响很小部分的代码,所以有没有办法减少我们编译的工作量呢?答案是肯定的,我们可以通过指定make target来仅编译我们需要的子项目。

SONiC中每个子项目生成的文件都可以在target目录中找到,比如:

  • Docker containers: target/.gz,比如:target/docker-orchagent.gz
  • Deb packages: target/debs//.deb,比如:target/debs/bullseye/libswsscommon_1.0.0_amd64.deb
  • Python wheels: target/python-wheels//.whl,比如:target/python-wheels/bullseye/sonic_utilities-1.2-py3-none-any.whl

当我们找到了我们需要的子项目之后,我们便可以将其生成的文件删除,然后重新调用make命令,这里我们用libswsscommon来举例子,如下:

# Remove the deb package for bullseye
rm target/debs/bullseye/libswsscommon_1.0.0_amd64.deb

# Build the deb package for bullseye
NOJESSIE=1 NOSTRETCH=1 NOBUSTER=1 make target/debs/bullseye/libswsscommon_1.0.0_amd64.deb

检查和处理编译错误

如果不巧在编译的时候发生了错误,我们可以通过检查失败项目的日志文件来查看具体的原因。在SONiC中,每一个子编译项目都会生成其相关的日志文件,我们可以很容易的在target目录中找到,如下:

$ ls -l
...
-rw-r--r--  1 r12f r12f 103M Jun  8 22:35 docker-database.gz
-rw-r--r--  1 r12f r12f  26K Jun  8 22:35 docker-database.gz.log      // Log file for docker-database.gz
-rw-r--r--  1 r12f r12f 106M Jun  8 22:44 docker-dhcp-relay.gz
-rw-r--r--  1 r12f r12f 106K Jun  8 22:44 docker-dhcp-relay.gz.log    // Log file for docker-dhcp-relay.gz

如果我们不想每次在更新代码之后都去代码的根目录下重新编译,然后查看日志文件,SONiC还提供了一个更加方便的方法,能让我们在编译完成之后停在docker builder中,这样我们就可以直接去对应的目录运行make命令来重新编译了:

# KEEP_SLAVE_ON=yes make <target>
KEEP_SLAVE_ON=yes make target/debs/bullseye/libswsscommon_1.0.0_amd64.deb
KEEP_SLAVE_ON=yes make all

Note

有些仓库中的部分代码在全量编译的时候是不会编译的,比如,sonic-swss-common中的gtest,所以使用这种方法重编译的时候,请一定注意查看原仓库的编译指南,以避免出错,如:https://github.com/sonic-net/sonic-swss-common#build-from-source

获取正确的镜像文件

编译完成之后,我们就可以在target目录中找到我们需要的镜像文件了,但是这里有一个问题:我们到底要用哪一种镜像来把SONiC安装到我们的交换机上呢?这里主要取决于交换机使用什么样的BootLoader或者安装程序,其映射关系如下:

Bootloader后缀
Aboot.swi
ONIE.bin
Grub.img.gz

部分升级

显然,在开发的时候,每次都编译安装镜像然后进行全量安装的效率是相当低下的,所以我们可以选择不安装镜像而使用直接升级deb包的方式来进行部分升级,从而提高我们的开发效率。

我们可以将deb包上传到交换机的/etc/sonic目录下,这个目录下的文件会被map到所有容器的/etc/sonic目录下,接着我们可以进入到容器中,然后使用dpkg命令来安装deb包,如下:

# Enter the docker container
docker exec -it <container> bash

# Install deb package
dpkg -i <deb-package>

参考资料

  1. SONiC Build Guide
  2. Install Docker Engine
  3. Github repo: sonic-buildimage
  4. SONiC Supported Devices and Platforms
  5. Wrapper for starting make inside sonic-slave container