태그 보관물: ci

Smoke test를 위한 GitHub Self-hosted Runner 설정

Unittest가 개발 과정에서 논리를 점검하는데 유용하기는 하지만 이세상에는 integration test가 담당해야 하는 부분도 엄연히 존재한다. 각각의 컴포넌트들이 잘 작성되었다 할지라도 이것을 통합했을 때의 동작을 보장하는 것은 또 다른 이야기 이기 때문이다.

이 부분을 커버하기 위한 목적으로 가상의 데이터를 생성하고 프로그램의 처음 시작 부분부터 마지막 출력 부분까지(E2E) 점검하는 스크립트를 만들어 두었다. 문제는 unittest 만큼이나 이 스크립트를 자주 돌리지 않을 뿐 아니라 종종 커밋할 때 까먹는 다는 것이다.

가만, 이런 귀찮을 걸 안까먹으려고 CI를 설정해 두지 않았던가!

몇몇 리소스들을 찾아보니 GitHub workflow에서 제공하는 VM에서 docker를 돌리는 것에는 문제가 없다고 한다. 그래서 기존에 Lint와 Unitest를 수행하던 CI script에 추가로 E2E를 위한 smoke test script를 수행하는 부분을 추가하고 돌려봤더니 안타깝게도 docker instance 생성 후에 pip로 패키지들을 설치하다고 공간이 모자라서 오류가 나고 있었다.

VM에서 띄우는 docker 자체는 동작하지만 디스크 공간 같은 리소스는 충분히 주어지지 않는 것이다. 보다 높은 데이터 공간을 위해 비싼 요금제를 사용하는 것도 고려해 볼 수 있겠지만, 매번 CI를 수행할 때마다 docker instacne들을 모두 재생성 한다면 시간도 오래 걸릴 것이어서 별로 합리적인 해결 방안은 아닐 것 같다.

그래서 생각한 결론은 Self-hosted runner를 살리는 것이다. 이것 용도로 컴퓨터를 설정해야 하고 항상 네트워크에 물려 있도록 해야 하는 귀찮음은 있지만 비싼 GitHub 요금제나 instance 생성에 걸리는 시간은 아낄 수 있을것 이라 생각해서 이다.

Self-hosted runner를 설정하는 과정은 어렵지 않다. GitHub의 Actions 항목에서 Runners -> Self-hosted runner를 선택하고 “New runner” 버튼을 누르면 자세한 설정 명령어가 나오는데, 그대로 복사 붙여넣기만 하면 된다.

기존의 Lint / Unittest를 수행하는 CI는 그대로 GitHub에서 수행하고, 새로 추가된 smoke test만 self-hosted runner에서 돌리도록 해서 총 6분정도가 소요된다. 이 정도면 쓸만하다.

결론

GitHub 요금제를 올리는 방법도 있지만, 스스로 유지보수가 가능하다면 self-hosted runner를 사용해서 docker instance의 재생성을 막고 부가적인 환경 설정을 자유롭게 하는 방법도 고려해 볼만 하다.

Rust: pbjson crate에서 “redundant reference” 경고

로컬에서 lint 검사 까지 마치고 서버로 push 했는데, 로컬에서 보이지 않았던 "redundant referece" 경고가 뜨면서 clippy를 수행하는 Rust용 CI가 실패 했다. 현재 프로젝트에서 Rust용 CI는 경고만 떠도 실패로 간주하는 -D warnings 파라미터를 설정하고 있는데 이것 때문에 CI 전체가 실패하는 문제가 생겼다.

로컬과의 차이점을 살펴 보니, local에서는 Cargo 1.94.1 버전을 사용하는데 반해, CI 서버에는 stable latest를 사용하도록 설정해 두어서 2026년 7월 현재 가장 최신 안정버전인 1.97.1을 사용하고 있었다.

이 버전에는 useless_borrow_in_formatting이라는 새로운 lint 항목이 추가 되었는데, 이것은 이미 reference인 변수에 중복해서 borrowing을 시도하는 경우 경고를 띄우는 검사항목이다.

문제가 발생하는 코드는 proto 파일로 부터 자동으로 serde Serialize / Deserialize 항목을 생성해 주는 pbjson이 만든 것으로, clippy는 원래 다른 모듈에 의해 생성되는 코드는 검사하지 않아야 하지만, 이 결과물을 include!() 매크로에 의해 현재 코드에 포함하기 때문에 clippy의 눈에 띄어서 문제가 되고 있는 것이다.

pub(crate) mod <모듈 이름> {
    // Include gRPC proto file.
    tonic::include_proto!("<proto 패키지 이름>");

    // Include pbjson-generated Serialize/Deserialize impls for the same type.
    include!(concat!(env!("OUT_DIR"), "/<생성된 proto 패키지 serde 파일>"));
} 

혹시나 이 문제에 대한 pbjon의 해결 패치가 있을까 해서 찾아 봤더니 2025년 12월 이후로는 버전 태그가 없었다.

결국 이 문제는 해결 하려면 3가지 정도의 해결 방법 있을 텐데,

  1. CI의 버전을 stable latest가 이닌 로컬 버전과 같은 1.94.1로 고정한다.
  2. 경고를 출력하더라도 실패로 보고 하지 않도록 clippy의 -D warning 파라미터를 제거한다.
  3. 경고를 출력하지 않도록 surpress한다.

이 중 3번을 적용하되 전체 범위가 아닌 include!() 구문에만 적용되도록 했다.

#[allow(unknown_lints)]
#[allow(clippy::useless_borrows_in_formatting)]
pub(crate) mod <모듈 이름> {
    // Include gRPC proto file.
    tonic::include_proto!("<proto 패키지 이름>");

    // Include pbjson-generated Serialize/Deserialize impls for the same type.
    include!(concat!(env!("OUT_DIR"), "/<생성된 proto 패키지 serde 파일>"));
}

#[allow(unknown_lints)]는 현재 버전의 clippy가 이해하지 못하는 lint feature가 있더라도 경고를 출력하지 말라는 의미이고, #[allow(clippy::useless_borrows_in_formatting)]가 바로 중복된 referrence를 발견하면 경고를 띄우는 해당 lint를 무시하도록 하는 디렉티브이다.

Release Please – Change log 생성 자동화

팀에서 Conventional commit을 따르고 있다면, 이것을 활용해서 change log를 만드는 것과 같은 귀찮은 일들을 자동으로 수행해 주는 도구들이 많이 있다. 이 포스팅에서는 그 중에서도 GitHub에서 사용하기에 편하다는 release-please를 Rust project에 적용해 본 내용을 다룬다.

사전 준비

  • GitHub PAT(Personal Access Token)

GitHub -> Settings -> Developer settings -> Personal access tokens -> Fine grained tokens에서 생성, contents, issues, pull-requests 권한을 부여한다. 이렇게 생성한 PAT를 Project -> Settings -> Environments에서 secret key로 등록해 둔다. 여기서는 PLEASE_REPLEASE_TOKEN 이라는 이름으로 설정했다.

  • Version Tag

Cargo.toml에 보면 version field가 있는데 현재 설정된 이 값에 맞춰서 GitHub에 Tag를 생성한다.

GitHub workflow 만들기

release-please는 workflow로 대기하면서 병합 브랜치(주로 main)에 코드가 들어 올 때 마다 이것을 감지해서 changelog를 작성하는 PR을 업데이트 한다. 이를 위해 다음과 같이 GitHub workflow를 하나 만들어 주고 서버로 commit한다. 아래의 예제에서 ci_keys는 앞서 생성한 PAT인 RELEASE_PELASE_TOKEN이 secret key로 들어 있는 environment이다.

동작확인

GitHub workflow가 main branch에 병합되면, 잠시 후에 release-package bot이 만든 PR이 만들어 지고, 여기에는 이전 버전 부터 현재까지의 변경 내역이 기록된다. 또한 이후에 main branch로 들어오는 모든 수정사항들도 기록된다.

사소한(?) 문제들

현재까지 발견한 두개의 문제점은:

  • 첫째. 리스트에 같은 커밋의 내용들이 두개씩 중복해서 나타난다.
  • 둘째. feat / fix 외의 다른 conventional commit들이 track되지 않는다.

첫번째 문제점의 원인은 PR을 merge할 때 default 값인 “Create a merge commit” 버튼으로 병합을 실행했기 때문에 작업 커밋 외에도 merge commit이 change log에 남았기 때문이다. release-please의 문서에서도 제안하듯, “Squash and merge”로 병합을 수행하면 커밋의 내용이 두번씩 중복되어 리스팅되는 문제를 막을 수 있다.

두번째 문제는 feat과 fix commit만 changelog에 올리는 것이 Release Please의 기본동작이기 때문인데 이것을 수정하고 싶다면 다음과 같은, release-please-config.json을 작성해서 어떤 항목들을 지원할 것인지 설정할 수 있다.

다음은 feat / fix외에도 perf / refactor / ci /chore 모두를 change log에 올리도록 하는 설정이다.

{
  "packages": {
    ".": {
      "release-type": "rust",
      "extra-types": ["feat", "fix", "perf", "refactor", "ci", "chore"],
      "bump-patch-for-types": ["refactor", "perf", "ci"],
      "bump-minor-pre-major": true,
      "changelog-sections": [
        { "type": "feat", "section": "Features", "hidden": false },
        { "type": "fix", "section": "Bug Fixes", "hidden": false },
        { "type": "perf", "section": "Performance Improvements", "hidden": false },
        { "type": "refactor", "section": "Code Refactorings", "hidden": false },
        { "type": "ci", "section": "CI Updates", "hidden": false },
        { "type": "chore", "section": "Miscellaneous", "hidden": false }
      ]
    }
  }
}

이 파일을 만들 때는 pair로 .release-please-manifet.json도 만들어 주는 것이 좋다고 해서 함께 만들어 주었다. 그 내용은 다음과 같다.

{
  ".": "1.0.1"
}

주의: release-please-config.json으로 제어할 때 주의해야 점이 있는데, 이 파일에서 release-type을 명시하고 있으므로, CI file에서는 release-type: rust반드시 삭제해 주어야 한다. 만약 이 부분이 있다면 충돌 때문에 설정한 config대로 동작하지 않고 default 동작으로만 동작 하게 될 수도 있다. 만약 잘 동작하지 않는다면 이부분을 확인해 보자.