yoncho`s blog

4. Premirrors & Shared State Cache | for 빌드 속도 개선 본문

기술, 나의 공부를 공유합니다./[Vehicle] Yocto

4. Premirrors & Shared State Cache | for 빌드 속도 개선

욘초 2024. 6. 20. 16:46

 

Yocto 3번 포스트 (Poky)에서 "bitbake core-image-minimal" 명령어를 실행 시키면 루프 파일 시스템 (rootfs) 이미지를 생성하기 까지 꽤 오랜시간이 소요되었음을 알 수 있다. 

오래 걸린 이유는 빌드에 필요한 데이터 소스를 외부에서 받아야하고, 단순히 프로그램 하나 빌드하는게 아닌 리눅스 S/W 스택 구성 전체 패키지도 빌드하기 때문이다.

그래서 우리는 다음과 같이 진행해 빌드 속도를 개선 할 것이다,.

I. 로컬 소스 저장소를 구축해 빌드에 필요한 데이터를 미리 적재해놓음으로써 빌드 시간을 개선하겠다.

II. Yocto가 제공하는 Shared State Cache(공유 상태 캐시)라는 캐시 저장소를 구축해 빌드 시간을 개선하겠다.

 

#목차

1. 소스 받기

2. Premirrors 구성 (Custom)

3. Shared State Cache 생성 (Custom)

4. 최종

 

 

[1] 소스 받기

우리가 앞서 진행한 Poky는 metadata를 읽어 bitbake가 빌드할 때 필요한 소스코드를 외부로부터 실시간으로 받아온다.

이때, http/https/ftp/sftp 등 많은 프로토콜을 지원하며 추가로 git과 같은 scm도 소스코드를 받을 때 사용할 수 있어야한다.

 

아래 그림은 oe-build system (bitbake)의 fetch 방법들이다.

**bitbake가 빌드 do_fetch 단계에서 외부 소스를 가져올 때 

'poky/meta/recipes-kernel/linux/linux-yocto_5.4.bb' 레시피 파일 안에 'SRC_URI' 변수에 정의되어있는 위치를 참고한다.

 

SRC_URI 정의된 장소에서 받은 소스는 DL_DIR 변수가 가리키는 경로에 저장된다. 

DL_DIR 변수는 'conf/bitbake.conf' 파일에 정의되어있으며, 기본적으로 'build/downloads' 경로를 가리킨다.

 

외부 소스가 저장되는 downloads/ 디렉터리로 가보면

*.tar.gz / *.tar.xz / *.done  확장자로 구성된 파일들이 존재한다.

여기서 *.done은 해당 패키지를 정상적으로 가져왔다는 의미이며 
혹여나 정상적으로 못 갖고오게되면 *_bad-checksum.. 과 같은 파일이 만들어진다.

 

download/ 디렉터리 아래 git2/ 디렉터리가 존재한다. git2/ 디렉터리 하위에는 git/ 디렉터리와 uninative/ 디렉터리가 존재한다.

build/
|-...
|-downloads/
	|--...tar.gz
    |--...tar.xz
    |--....done
    |--git2/
    |--uninative/

bitbake가 Git을 통해 소스를 받을 때 git2/ 디렉터리 아래에 저장된다.

uninative는 C 라이브러리 중 glibc를 말하며, 호스트 배포판의 라이브러리로 부터 빌드 시스템을 격리하는데 사용한다.

=> 네이티브 레시피가 호스트 배포판에서 제공하는 glibc 버전이 아니라 Yocto 프로젝트에서 배포하는 glibc 버전을 사용한다. 이를 통해 호스트 환경과 상관없이 Yocto가 제공해주는 glibc를 사용함으로써 환경 차이가 발생하지 않게 된다.

 

추가로 git으로 부터 소스를 받으면 git 주소와 동일한 이름에 .done이 생기게된다! 

나중에 bitbake가 빌드 시 .done 파일이 확인되면 레시피에서 지정한 'S' 변수가 가리키는 위치로 해당 소스가 복사된다.

 

아래 내용은 do_fetch (bitbake build 과정 中) 프로세스를 설명한 것이다.

I. SRC_URI에 정의된 소스가 Local에 저장되어있는지 DL_DIR 디렉터리 (기본: build/downloads) 확인 

II. DL_DIR에 소스가 없다면 PREMIRROS 변수에 지정된 MIRROR(Custom or Site)로 부터 다운로드 시도

III. MIRROR로 부터 다운로드 안될 시 Upstream 소스 (SRC_URI에 정의된 곳)로 부터 다운로드 시도

IV. Upstream으로 부터 다운로드 안될 시 MIRRORS 변수 정의된 Site로부터 다운로드 시도

V. MIRRORS Site로 부터 다운로드 안될 시 Bitbake는 Error 반환

 

DL_DIR 지정 경로 혹은 MIRRORS에서 소스 다운로드가 완료되면 do_unpack Task에서 'S'변수가 가리키는 디렉터리로 받은 소스를 다운로드한다. 

만약, git remote repository에서 소스를 받을 경우 do_unpack Task에서는 checkout(이동)이 이루어진다.

*'S'변수는 압축해제, 패치, 컴파일이 진행되는 디렉터리이다.

 

 

[2] Premirrors 구성 (Custom)

Bitbake가 빌드 시 필요한 외부 소스들을 실시간으로 다운로드 받게되는데.

빌드 할 때마다 오랜 시간이 걸리고, 혹여 팀프로젝트의 경우 모두가 같은 버전의 파일을 받아야할 때가 있으므로

별도 저장소를 구축해 편리하게 빌드를 할 수 있게 될 것이다.

 

참고로 Bitbake명령어 중 빌드를 하지 않고 do_fetch (외부로부터 빌드에 필요한 소스를 다운 받는 단계)까지만 진행할 수 있는데. 아래 명령어가 그 방법이다.

bitbake core-image-minimal --runall=fetch

'--runall=<task_name>'을 하게되면 task_name까지만 진행하게 되는 것이다!

 

git repository로 부터 다운 받은 파일을 tarball (.tar.gz) 형태로 저장하겠다. 

tarball로 해야 압축되어 파일 용량이 줄어들기 때문..!

tarball로 받기 위해선 아래 설정이 필요하다.

build/local.conf 파일 맨 마지막 줄에 아래 값을 추가한다.

...
# compress tarballs for mirrors
BB_GENERATE_MIRROR_TARBALLS = "1"

위 설정이 완료되면 앞으로 git repository로부터 다운 받은 소스들은 tarball로 생성된다.

 

위 설정을 테스트 하기 위해 downloads/ 디렉터리를 지우고 다시 fetch를 진행해보자.

#build/downloads 디렉터리 제거
rm -rf downloads

#refetch
bitbake core-image-minimal --runall=fetch -f

위 명령어를 수행 했으면 downloads/ 디렉터리 아래 git에서 받은 소스가 git2/ 디렉터리 하위에 저장되는게 아니라

downloads/ 디렉터리 바로 아래 <git_repository>.tar.gz으로 저장된다.

 

이제 자체 Mirrors를 구축하기 위해 아래 단계를 진행해보자.

 

#CUSTOM MIRRORS 구축 

I. build/downloads/ 디렉터리 아래 *.done 파일을 모두 제거한다.

rm -rf build/downlaods/*.done

II. build/downloads/ 디렉터리 아래 git2/ 디렉터리를 제거한다.

rm -rf build/downlaods/git2/

III. 프로젝트 최상위 (poky_src/) 디렉터리 아래 source_mirror/ 디렉터리를 생성한다.

mkdir ../poky_src/source_mirrors/

위 생성한 source_mirrors/ 디렉터리가 앞으로 우리가 사용할 자체 Mirrors 이다.

 

IV. 생성한 Mirrors 디렉터리 경로 지정을 위해 local.conf파일 하단에 아래 값을 추가해야한다.

# Specify own PREMIRRORS location
INHERIT += "own-mirrors"

SCOURCE_MIRROR_URL = "file://${COREBASE}/../source_mirrors"

own-mirrors.bbclass 파일은 INHERIT 변수에 할당하고, SOURCE_MIRRORS_URL 변수를 사용한다.

여기서 own-mirrors.bbclass 파일은 'poky/meta/classes/own-mirrors.bbclass'에 위치한다.

SOURCE_MIRROR_URL은 own-mirrors.bbclass 클래스 파일에 선언된 변수이고, 이 변수가 자체 PREMIRRORS 디렉터리 경로를 할당받는다. 

 

자체 PREMIRRORS는 III단계에서 생성한 디렉터리를 할당해야하는데 그 앞에 'file://' 프로토콜 지시자를 추가한다. 

그 이유는 우리가 호스트에 구성했기 때문에 빌드 시스템이 파일 또는 경로를 찾을 때 빌드 호스트(로컬)에서 찾을 수 있게 하기 위함이다. 즉, file://은 빌드 호스트를 base로 생각한다.

 

COREBASE 변수는 meta 디렉터리의 부모를 가리킨다. 즉, poky 디렉터리 절대 경로를 갖고있는 변수이다.

(앞서 poky 포스트에서 배웠다. poky에는 oe-core, meta, meta-yocto 등이 있다...라는걸)

 

따라서, SOURCE_MIRROR_URL은 source_mirrors/ 디렉터리의 절대 경로를 가지고 있는 것이다.

 

위 설정을 통해 우리는 bitbake가 빌드 시 외부 소스 데이터를 로컬에 구축한 Mirrors에서 가져오게 했다. 

 

그럼 테스트 하기 위해 downloads/ 디렉터리를 지우고 다시 fetch를 진행해보자.

#build/downloads 디렉터리 제거
rm -rf downloads

#refetch
bitbake core-image-minimal --runall=fetch -f

위 결과를 보게되면 기존보다 엄청 빠른 속도로 빌드 하는걸 볼 수 있다.

실제 프로젝트에서는 해당 PREMIRRORS를 git repository로 팀원끼리 공유한다.

 

Premirrors에서 소스를 가져오게되면 build/downloads/ 디렉터리 아래 소스 파일들은

실제 다운 받은 파일이 아니라 PREMIRRORS 파일들을 symbolic link하는 파일들이다. 

 

 

[3] Shared State Cache 생성 (Custom)

Premirrors로 do_fetch 단계 속도가 엄청 향상된 것을 우린 알았다..

여기서 빌드 시간 단축 방법 II. Shared State Cache를 배울 것이다..!! 그럼 더 단축되겠지? ㅎ

 

공유 상태 캐시를 논하기 전에 아래 bitbake 빌드 과정 중 일부를 보면서 이야기 하겠다.

bitbake는레시피의 각 태스크 수행 시 Signature 값을 만든다. 이 Signature 갑은 태스크 코드/ 변수 등 입력 metadata로 부터 생성된다.

이 Signature와 빌드 결과를 Object형태인 Shared State Cache를 만들어 특정 디렉터리에 저장한다.

이 과정을 하는 이유는 빌드 과정에서 실행 완료된 Task를 다시 실행할 때 실행 시점에 계산된 Signature값과 기존에 저장된 Signature값을 비교해 동일하거나 앞서 생성된 Object가 존재하고 재사용 가능하다 판단되면 해당 Task를 건너 뛴다.

=> 이미 한번 작업이 완료된 Task (단, 내용이 변경되지 않음)는 Cache내용을 재사용하는 것!

 

Signature값은 기존 대비 입력이 변경되지 않으면 동일하며

Signature값이 같다는 건 곧, 이미 수행돼 공유된 캐시를 그대로 재사용해도 된다는 의미이다.

 

이때 수행된 Task 결과를 가져오는 Task를 setscene Task라 한다.

setscene Task는 'do_<task_name>_setscene' 형태 이름을 갖는다.

 

아래 목록은 setscene이 지원되는 Task 목록이다.

I. do_packagedata (do_packagedata_setscene) :  최종 패키지 생성을 위해 빌드 시스템에 의해 사용되는 패키지 메타데이터 생성

II. do_package (do_package_setscene) : do_install Task에 의해 생성된 파일을 이용할 수 있는 패키지들과 파일들에 근거해 나눔

III. do_package_rpm (do_package_rpm_setscene) : rpm패키지를 생성 후 패키지 피드에 배치

IV. do_populate_lic (do_populate_lic_setscene) : 이미지 생성 시 모아놓은 레시피를 위한 라이선스 정보 생성

V. do_populate_sysroot (do_populate_sysroot_setscene) : 다른 레시피에 의해 이용될 수 있게 do_install Task에 의해 설치된 파일을 sysroot로 이동

VI. do_package_qa (do_package_qa_setscene) : 패키지로 만들어진 파일 QA검증

 

 

build/ 디렉터리 아래 보면 sstate-cache/ 디렉터리를 확인할 수 있다. 해당 디렉터리가 바로

Shared State Cache가 저장되는 곳이다. 

그리고 이 경로를 지정하는 변수는 SSTATE_DIR 이다.

 

bitbake 명령어 중 특정 변수를 찾는 '-getvar'를 이용한 아래 명령어를 수행해보자.

bitbake-getvar -r core-image-minimal SSTATE_DIR

아래와 같은 결과를 얻을 수 있다.

 

**Custom Shared State Cache를 만들기 위해선 아래 두개 변수를 알고있어야한다.

I. SSTATE_DIR : 레시피 의 각 Task 수행 시 Sinature와 결과에 대한 Object가 생성되고 해당 Object를 SSTATE_DIR 변수에 정의된 경로에 저장한다.

I. SSTATE_MIRRORS : Shared State Cache 저장소로 사용되는 디렉터리의 경로를 가지고 있다. 이 변수가 설정되면 

bitbake가 빌드 시 Task 수행하기전 이미 실행된 Task인지 해당 경로에서 확인한다.

 

 

setscene이 적용되는걸 확인하기 위해 아래 과정을 진행해보자.

명령어는 이미 앞에서 많이 해봤기 때문에 생략한다.

I. core-image-minimal 이미지 레시피 빌드

II. tmp 디렉터리 삭제 (참고: tmp 디렉터리는 oe-build system의 빌드 결과물을 저장하는 경로이다)

tmp 디렉터리 경로는 TMP_DIR변수에 저장된다.

III. core-image-minimal 이미지 레시지 재빌드

하게되면 아래와 같은 setscene이 적용된 걸 확인할 수 있고, 빌드 시간이 단축된걸 느낄 수 있다.

 

#CUSTOM Shared State Cache 구축

I. 프로젝트 최상위 디렉터리에 sstate-cache/ 디렉터리 생성

(*최상위 디렉터리 : poky_src/)

mkdir ../poky_src/sstate-cache/

II. build/sstate-cache/ 디렉터리 아래 모든 파일을 방금 만든 poky_src/sstate-cache로 복사

cp -r ../build/sstate-cache/* ../poky_src/sstate-cache

III. build/conf/local.conf 파일 맨 마지막줄에 아래 내용을 추가한다.

...
# make shared state cache mirror
SSTATE_MIRRORS += "file://.* file://${COREBASE}/../state-cache/PATH"

SSTATE_MIRRORS 변수에 경로 지정 시 '/PATH' 를 꼭 추가해야한다. 이유는 빌드시 'PATH'는 빌드 시스템에 의해 자동으로 hash의 처음 두 문자로 이름 지어진 디렉터리로 대체된다.  (이건 왤까...?)

 

*참고: 

shared state cache repository는 외부 서버에도 구축할 수 있다.

아래 내용은 외부 서버도 추가하는것인데.  

중요할 점은 외부 서버 지정 시 맨 끝에 '\n'을 빠뜨리면 안된다. (단, kirkstone 버전의 경우 생략 가능)

SSTATE_MIRRORS ?= "\
file://.* http://<server>/share/sstate/PATH;downloadfilename=PATH \n \
file://.* file://<local_dir>/local/dir/sstate/PATH"

 

IV. 테스트를 하기 위한 준비 1. local.conf 원형 파일 존재

local.conf에 값을 설정해놓고 테스트 하기 위해 build/ 디렉터리를 제거하게되면 local.conf 파일이 build/conf/ 디렉터리 안에 있기 때문에 같이 제거된다. 이 문제를 해결하기 위해 우리는 빌드 환경 초기화 명령어(oe-init-build-env)를 최상위 디렉터리에서 수행하면 local.conf파일이 생성된다. 하지만 해당 파일에는 우리가 이전에 적용했던 내용이 없을 것이다.

=> bitbake는 oe-init-build-env 명령어를 실행하면 어디선가 local.conf 파일을 복사해 conf/ 디렉터리 아래에 복사한다.

복사해온 원본 파일은 어디 있을까?

바로 poky/meta-poky/conf/ 디렉터리 아래 'local.conf.sample' 파일이 local.conf 파일의 원본이다.

그래서 우리는 local.conf에 항상 어떤 설정을 하고 싶으면 'local.conf.sample'파일에 해놓는게 좋다.

 

V. 테스트를 하기 위한 준비 2. local.conf 원형 파일(local.conf.sample)에 미리 값 설정하기

local.conf.sample 파일 (poky/meta-poky/conf/local.conf.sample)에 아래 내용을 추가하자

# Specify own PREMIRRORS location
INHERIT += "own-mirrors"
SOURCE_MIRROR_URL = "file://${COREBASE}/../source-mirrors"

# compress tarballs for mirrors
BB_GENERATE_MIRROR_TARBALLS = "1"

# make shared state cache mirror
SSTATE_MIRRORS = "file://.* file://${COREBASE}/../sstate-cache/PATH"
SSTATE_DIR = "${TOPDIR}/sstate-cache"

 

 

VI. 테스트를 하기 위한 준비 3. build/ 디렉터리 삭제 => 빌드 환경 초기화 => 빌드 (확인)

#build/ 디렉터리 삭제
rm -rf build/

#빌드 환경 초기화
source poky/oe-init-build-env

#빌드 
bitbake core-image-minimal

위 모든 설정을 마치고 빌드하게되면 아래와 같이 결과가 표시된다.

자세히 보면 'Sstate summary: ..' 라인에  (99% match)를 확인할 수 있다. 이게 바로 빌드에 필요한 Task 수행 과정 중 99%가 이미 수행된 적 있고 결과를 가져올 수 있다는 걸 의미한다.

그리고 index (0,1,2 ..)가 붙은 로그쪽을 보면  <task_name>_setscene을 확인할 수 있다.

이게 바로 Shared State Cache 를 사용 중임을 알 수 있다.

 

 

+ 번외

PREMIRRORS 와 Shared State Cache는 팀 프로젝트 뿐만 아니라 개인 프로젝트에서도 유용하다.

2개 이상의 동일 프로젝트를 동일 호스트에 저장하고 있다고 가정하면, 두개 프로젝트가 하나의 PREMIRRORS, Shared State Cache를 바라보게 하면 된다.  추천하진 않지만 이런 방법도 있다고 알고있자.

**BB_ENV_EXTRAWHITE : 이 변수는 셸 환경 변수를 bitbake 전역 환경 변수로 만들어 주는 방법을 제공해준다.

 

I. 셸 환경 변수 설정을 위해 ~/.bashrc 설정

SOURCE_MIRROR_URL과 SSTATE_MIRRORS 변수를 셸 환경 변수로 만들고, bitbake가 이 변수를 사용할 수 있게 

~/.bashrc에 아래 내용을 추가하자.

...
export BB_ENV_EXTRAWHITE="${BB_ENV_EXTRAWHITE} SOURCE_MIRROR_URL SSTATE_MIRRORS"
export SOURCE_MIRROR_URL="file://${HOME}/data/source-mirrors"
export SSTATE_MIRRORS="file://.* file://${HOME}/data/sstate-cache/PATH"

II. local.conf 원본(local.conf.sample) 파일 내용 中 SOURCE_MIRROR_URL과 SSTATE_MIRRORS 내용 제거

 poky/meta-poky/conf/local.conf.sample 파일에 작성한 사전 설정 내용 중  셸 변수로 설정해준 변수를 주석 처리 한다

...
# SOURCE_MIRROR_URL = ...
...
# SSTATE_MIRRORS = ...

 

III. Custom PREMIRRORS와 Custom Shared State Cache 폴더 복사

~/.bashrc 에 추가한 내용을 보면 bitbake안에 있는 내용이 아니라 호스트 로컬에 있는 source-mirrors와 sstate-cache를 각각 변수에 추가해준다.

이를 위해 기존 poky_src/source-mirrors/ 디렉터리와 poky_src/sstate-cache/ 디렉터리를 home/data/디렉터리 아래로 복사해준다.

sudo cp -r source-mirrors/ /home/data/source-mirrors/
sudo cp -r sstate-cache/ /home/data/sstate-cache/

 

IV. 실행

매우 빠른 속도로 빌드가 되는걸 확인할 수 있다.

 

 

Shared State Cache에 대한 내용은 더 해야하지만 추후 다른 포스트에서 추가로 자세히 다루겠다.

 

 

[4] 최종

oe-build system은 이미지 빌드에 필요한 소스를 빌드 할 때 실시간으로 받아온다.

각 소스는 다양한 방법(서버, 사이트 등)으로 받아오기 때문에 다양한 프로토콜 또한 지원이 되어야한다.

oe-build system은 resource와 관련된 모든 걸 자동화 해준다.

 

특히, 빌드 시 많은 소스를 외부로부터 받아오기 때문에 시간이 오래 걸리는데

이를 해결하기 위한 방법은 다음과 같다.

I. PREMIRROR 구축

II. Shared State Cache 구축 

 

그리고 위 방법들을 사용하면 빌드 시간이 확실히 줄어든다.

 

 

이번 포스트와 앞선 포스트 bitbake, poky 등을 배워오면서 우리는 bitbake의 build 디렉터리 구성에 대해 확실히 알고 가야한다.

아래 내용을 꼭 기억하자!!!!

build/
|-- cache : bitbake parser cache (recipe, conf 파일 파싱 결과 저장) 
|-- conf : 환경 설정 파일
|-- downloads : 다운로드된 source cache (DL_DIR)
|-- sstate-cache : binary build cache (SSTATE_DIR)
|--tmp : 모든 빌드에 대한 결과물 저장 디렉터리 (TMPDIR)

꼭!!!

Comments