지난 1부 연구에서는 기여자가 apple/container 프로젝트를 처음 접하고, 개발 환경을 설정하며 기여를 결심하게 된 배경, 그리고 Issue 선정에 대한 내용을 다루었다.
이제 Issue는 선정했다.
'상대 경로 입력 시 에러가 난다' -> '경로를 변환해주면 된다' 라는 생각을 가지고 접근했다.
이번 편에서는 PR을 올리기까지의 과정, 그리고 메인테이너의 피드백을 받기까지의 과정에 대해 다룬다.
문제 정의 및 기술적 배경: Issue #962
1. apple/container 아키텍쳐
먼저 apple/container 기술적 기반을 다시 정리해보자.
이 프로젝트는 일반적인 리눅스 컨테이너 런타임(Docker, Podman 등)과 달리, macOs의 Virtualization.framework를 활용하여 경량화된 가상머신(VM) 위에서 리눅스 컨테이너를 구동한다.
| 구성 요소 | 기술 스택 | 역할, 특징 |
| CLI (apple/container) | Swift (macOS Host) | 사용자의 명령을 파싱하고, 컨테이너 구동을 위한 설정을 조율한다. |
| Framework(apple/containerization) | Swift (macOS/Linux) | VM 라이프사이클 관리, 네트워크 설정, 스토리지 마운트 등을 담당하는 로직 |
| Guest INIT (vminitd) | Swift (Linux Guest) | VM 내부에서 최초로 실행되는 프로세스(PID 1) - 실제 컨테이너 프로세스를 fork/exec 하는 역할이다. |
이러한 구조는 호스트(macOS)와 게스트(Linux VM) 2가지 실행 컨텍스트가 분리되어 있음을 나타낸다.
Issue에 대한 원인 중 파일 경로 처리(Path Resolition)에 있어 복잡성을 유도했을 가능성이 높다고 볼 수 있다.
2. Issue #962: relative path in 'entrypoint' is not accepted
해당 Issue를 등록한 사용자의 글을 봐보자.
사용자는 터미널 환경에서의 관습에 따라 상대 경로(예: ./rustc)를 사용하여 컨테이너 내의 바이너리를 실행하고자 했다.
- 사용자 입력: container run -w /usr/local/cargo/bin --entrypoint./rustc rust:latest --version
- 기대 동작: 컨테이너 내부의 /usr/local/cargo/bin 디렉토리로 이동한 후, 그곳에 위치한 rustc를 실행.
- 실제 동작: Error: internalError: "failed to find target executable./rustc".
실제 동작을 보면 시스템이 ./rustc라는 문자열을 해석하는 과정에서, 이를 유효한 실행 파일 경로로 인식하지 못했음을 나타낸다.
그럼 이 '인식 실패' 발생 지점은 어딜까?
첫번째 시도: Parser에서 오류가 난걸까?
인식 실패가 발생하는 지점을 '문자열 파싱 문제'라고 생각했다.
입력값이 잘못 처리되었으니, 입력을 받는 CLI쪽에서 찾아보자. 라고 생각을 했었다.
아래와 같이 정리할 수 있겠다.
- 사용자가 --entrypoint에 상대 경로를 넣으면 에러가 난다.
- CLI가 이 상대 경로를 그대로 백엔드에 넘겨주기 때문이다. 백엔드는 절대 경로만 원할 것이다.
- CLI에서 사용자가 입력한 경로가 상대 경로인지 확인하고, 만약 그렇다면 절대 경로로 변환해서 넘겨주자.
즉, Parsing하는 부분에서 Static Path인지, Relative Path인지를 확인하고, 이를 분기처리하면 될 거라고 생각했다.
아무튼, 각설을 하고 수정을 한 코드는 아래와 같았다.
Sources/Services/ContainerAPIService/Client/Parser.swift
let processArguments: [String]? = {
var result: [String] = []
var hasEntrypointOverride: Bool = false
// ensure the entrypoint is honored if it has been explicitly set by the user
if let entrypoint = managementFlags.entrypoint, !entrypoint.isEmpty {
result = [entrypoint]
if entrypoint.hasPrefix("/") {
result = [entrypoint]
} else {
let resolved = URL(fileURLWithPath: workingDir)
.appendingPathComponent(entrypoint)
.standardized
.path
result = [resolved]
}
hasEntrypointOverride = true
} else if let entrypoint = config?.entrypoint, !entrypoint.isEmpty {
result = entrypoint
if let first = entrypoint.first, !first.hasPrefix("/") {
var resolved = entrypoint
resolved[0] =
URL(fileURLWithPath: workingDir)
.appendingPathComponent(first)
.standardized
.path
result = resolved
} else {
result = entrypoint
}
}
if !arguments.isEmpty {
result.append(contentsOf: arguments)
} else {
if let cmd = config?.cmd, !hasEntrypointOverride, !cmd.isEmpty {
result.append(contentsOf: cmd)
if let first = cmd.first, !first.hasPrefix("/") {
var resolved = cmd
resolved[0] =
URL(fileURLWithPath: workingDir)
.appendingPathComponent(first)
.standardized
.path
result.append(contentsOf: resolved)
} else {
result.append(contentsOf: cmd)
}
}
}
return result.count > 0 ? result : nil
}()
그 후 Unit Test도 작성을 하고, 문제가 없다는 것을 확인하고 PR을 올렸었다.
PR을 올릴 때 open source의 경우 guideline이 존재한다.
- container, containerization 모두 동일한 가이드라인을 가진다.
- 여기서 하라는 대로만 잘 하면 된다.
https://github.com/apple/containerization/blob/main/CONTRIBUTING.md
containerization/CONTRIBUTING.md at main · apple/containerization
Containerization is a Swift package for running Linux containers on macOS. - apple/containerization
github.com
[미국? 해외 어디서 근무하시는지는 모르지만, 연휴라서 그랬는지(크리스마스) 피드백이 오기까지 약 2주 정도 걸렸다.]
Contributor의 피드백 (1차)
2주 정도가 지나니 나도 까먹고 있엇다... 그러다가 갑자기 github 알람을 확인했는데 답변이 달려있었다..!

Thanks! 라는 답변이 달렸다.
(이때는 잘 몰랐는데 Contributor가 Thanks를 달면 긍정적인 신호라구...)
아래에 피드백이 2가지 달려있었다. 요약을 해보자면
1. 공통 기능을 함수로 빼라 <- 솔직히 이거보고 너무 부끄러웠음;;; 너무 긴장해서 그런가 검증하고 공통 부분을 모아주질 않았다.
2. This doesn't cover all cases. <- 모든 케이스를 다 커버하지 않는다. 다시.
그래서 호다닥 함수 extract하고~ 테스트 케이스도 다시 검증해서 제출을 했다.

그런데...
Contributor의 피드백 (2차)
몇 시간 지나지 않아 새로운 알림이 와있었다.

?? 이때까지만 해도 나는 container 딴에서 문제가 있는 줄 알았는데...
자세히 댓글을 봐보자.

요약을 해보자면
- 당신의 수정사항이 작동은 하지만, Bundle의 경로를 수정하는 방식이므로
- containerization 레이어에서 문제를 해결하는 것이 더 깔끔할 것 같다는 의견
- vmexec.swift의 74번째 줄에서 실패가 발생한다.
라고 답변이 왔다.
뭐 간단히 말해서 컨테이너 안에서 실제로 명령어를 실행해 주는 vmexec라는 프로그램을 고치자.. 라고 볼 수 있을 거 같다.
그리고 해당 내용이 공식 문서에 없으니, 이슈를 새로 만들어 docs를 만들어주겠다는 내용이었다.
여기서부터 뭔가 내가 감당할 수 없을 거 같다는 생각이 듦.
정리를 해보자
우리는 앞서 container, containerization, vminitd에 대해서 살펴봤었다.
- CLI는 사용자의 의도를 전달하는 메신저일 뿐, 실행 로직을 결정하는 주체가 아니다.
- 실행 가능한 경로인지 판단하고 해석하는 것은 실제 실행을 담당하는 프레임워크의 몫이다.
그래서 containerization에서 문제를 해결해야 된다고 Contributor가 답을 준 것 같았다.
하라는 대로 해보자
친절히도 Contributor가 작업 방식에 대해 댓글을 남겼다.

- Check out a copy of containerization.
- From your container project: `swift package edit --path /path/to/your/containerization containerization
- container system property set image.init vminit:latest
- In your container project: make all install
- container system stop ; container system start
하라는 대로 그대로 해보자.
여기서 많은 문제가 있었다... 정말
- apple/container와 같이 커널 레벨을 건드리는 시스템 소프트웨어는 수정 후 빌드하고 검증하는 과정 자체가 복잡했다.
- 기존 프젝 때문에 다시 Swift SDK로 6.0.3을 썼는데 6.2.* 을 요구해서 맞춰주고~
- container는 기존 남아있는 작업물 때문에 계속 죽고..네트워크 버그, Swift 리눅스 SDK 버전 불일치, 체크섬 오류 등 수많은 빌드 에러와 실행 크래시... 으악!
물론 Claude, Gemini CLI와 함께 모든 프로세스를 다 죽이고, Zombie가 생기는 문제를 해결해서 겨우 실행 환경을 다시! 구성했다.
그 후 Contributor가 말하는 대로 vmexec.swift에서 문제가 발생하는 지점에서 수정을 해보자.

위와 같이 수정을 진행했다.
문제, 해결책을 요약해보자면
- 문제: 실행기(vmexec)가 작업 디렉토리로 이동(chdir)하기도 전에 상대 경로로 파일 존재 여부를 체크해서 실행을 차단해버림.
- 해결책: 파일 체크 전, 작업 디렉토리와 상대 경로를 미리 합쳐 실제 절대 경로를 계산해내도록 로직을 수정함.
이 부분은 containerization에서 진행을 했다.
원래 타겟으로 정했던 container에서도 기존 ParserTest.swift + Contributor가 준 케이스를 테스트해 통과됨을 확인했다.
그래서 containerization에서 PR을 날리고, 기존 container의 PR은 다시 reset을 시킨다음, Unit Test에 추가된 형태와 함께 PR 내용을 수정했다.
[containerization에 날린 PR]
https://github.com/apple/containerization/pull/473
Fix: resolve relative executable paths against workingDirectory in vmexec by ParkSeongGeun · Pull Request #473 · apple/contain
Summary Fixes the root cause for apple/container#962. This PR fixes an issue where relative paths provided as an entrypoint or command (e.g., ./uname) failed to execute. The root cause was that exe...
github.com
[Update된 PR, Comment]

이제는 대기중이다... 후... 이게 merge되었으면...
이제는 Contributor의 답장을 기다릴 일만 남았다.
이렇게 오픈소스 컨트리뷰트 도전 과정에 대한 글을 마무리하려고 한다.
- 답장이 오면 그에 대한 포스팅을 할 예정이다.
'Infra' 카테고리의 다른 글
| 오픈 소스 기여 도전기 Ep.1 (2) | 2026.01.11 |
|---|---|
| 네트워크 자문자답 (0) | 2025.12.29 |
| Spring Boot + Docker + AWS 배포 자동화 환경 구축 (0) | 2025.12.28 |