자동화가 중요한 연속 통합(Continuous Integration, CI) 및 연속 배포(Continuous Deployment, CD) 세계에서 Makefile을 적절히 사용하면 구현 및 인프라 엔지니어링의 효율이 크게 향상될 수 있다. 주로 C 및 C++ 같은 언어의 코드 컴파일에 사용되는 도구로 알려진 Makefile은 CI/CD 파이프라인의 다양한 작업을 단순화하고 자동화하는 데 유용한 다목적 도구이다. 이 가이드는 초보자와 중급 소프트웨어 및 DevOps 엔지니어에게 Makefile을 CI/CD에서 효과적으로 사용하는 방법을 소개하고, 최선의 방법, 일반적인 함정, 주의할 점 등을 다룬다.

Makefile이란 무엇인가?
Makefile은 보통 make 빌드 자동화 도구와 함께 사용하는 쉘 명령을 포함하는 특별한 파일이다. 전통적으로 코드를 컴파일하는 데 사용되지만, Makefile의 유연성 덕분에 CI/CD 파이프라인을 포함한 다양한 자동화 작업에 적합하다.
CI/CD에서 Makefile을 사용하는 이유
- 보편성: make는 대부분의 서버 머신과 Linux 배포판에 기본적으로 설치되어 있어, 추가 설치 없이 쉽게 접근할 수 있는 도구이다.
- 일관성: 명령과 순서를 한 번 정의하면, 매번 동일한 방식으로 실행된다.
- 단순성: 복잡한 명령 시퀀스를 간단한 make 명령으로 단순화할 수 있다.
Makefile의 구성 요소
Makefile은 주로 다음과 같은 구성 요소로 이루어진다:
- 타겟(Target): 실행할 작업의 이름이다.
- 의존성(Dependency): 타겟이 실행되기 전에 완료되어야 하는 작업이다.
- 명령(Command): 타겟이 실행될 때 수행할 실제 작업이다.
target: dependencies
command
Makefile을 활용한 CI/CD 자동화
1. 기본 빌드 스크립트 작성
CI/CD 파이프라인에서 Makefile을 사용하는 가장 기본적인 예는 빌드 스크립트를 작성하는 것이다. 예를 들어, 다음은 간단한 파이썬 프로젝트를 빌드하고 테스트하는 Makefile이다:
.PHONY: all build test
all: build test
build:
pip install -r requirements.txt
test:
pytest
2. 환경 설정 자동화
Makefile을 사용하여 환경 설정 작업을 자동화할 수 있다. 예를 들어, Docker 컨테이너를 설정하는 명령을 Makefile에 정의할 수 있다:
.PHONY: build-docker run-docker
build-docker:
docker build -t myapp .
run-docker:
docker run -d -p 8000:8000 myapp
3. 배포 자동화
CI/CD 파이프라인에서 배포 작업도 Makefile을 통해 자동화할 수 있다. 다음은 Kubernetes 클러스터에 애플리케이션을 배포하는 예시이다:
.PHONY: deploy
deploy:
kubectl apply -f deployment.yaml
Best Practices
- 명확한 타겟 이름 사용: 타겟 이름은 그 작업이 무엇을 하는지 명확히 나타내야 한다.
- 의존성 관리: 타겟 간의 의존성을 명확히 정의하여 빌드 순서를 관리한다.
- 포괄적인 문서화: Makefile에 주석을 추가하여 각 명령이 하는 일을 설명한다.
Common Pitfalls
- 탭과 공백 혼동: Makefile에서는 명령어 앞에 반드시 탭이 와야 한다. 공백과 혼동하지 않도록 주의한다.
- 의존성 누락: 의존성을 명확히 정의하지 않으면 빌드 순서가 꼬일 수 있다.
- 복잡한 스크립트: Makefile이 지나치게 복잡해지지 않도록 주의한다. 필요시 스크립트를 분리하거나 외부 스크립트를 호출한다.
주의할 점
1. 환경 변수 관리:
- 환경 변수를 Makefile에 직접 하드코딩하는 것은 피해야 한다. 대신 .env 파일을 사용하거나 CI/CD 도구에서 환경 변수를 설정한다
- 민감한 정보를 포함한 환경 변수를 보호하기 위해 적절한 접근 제어를 설정한다.
2. 에러 처리:
- Makefile 내에서 발생할 수 있는 에러를 적절히 처리하는 것이 중요하다. || true 또는 && 연산자를 사용하여 명령이 실패할 경우의 동작을 정의한다.
- 에러 발생 시 로그를 기록하고, 디버깅에 도움이 되도록 충분한 정보를 제공한다.
3. 플랫폼 간 호환성:
- Makefile을 작성할 때, 사용하는 명령어와 경로가 플랫폼에 따라 다를 수 있다는 점을 고려한다. 가능하면 플랫폼 독립적인 명령어를 사용하고, 필요시 조건부 논리를 추가하여 각 플랫폼에 맞게 설정한다.
- 예를 들어, Windows와 Unix 계열 시스템에서 경로 구분자가 다르기 때문에 이를 처리하는 로직을 추가한다.
4. Makefile의 크기와 복잡성 관리:
- Makefile이 지나치게 커지거나 복잡해지면 유지보수가 어려워진다. 큰 프로젝트에서는 여러 개의 Makefile로 분리하거나, 포함 지시어(include)를 사용하여 모듈화한다.
- 예를 들어, 각 서브디렉토리에 Makefile을 두고, 최상위 디렉토리에서 이를 포함하여 빌드 순서를 관리할 수 있다.
5. 타겟 간의 의존성 명확히 하기:
- 타겟 간의 의존성을 명확히 정의하여 빌드 순서를 올바르게 유지한다. 의존성이 누락되면 빌드 과정에서 예기치 않은 에러가 발생할 수 있다.
- make의 -j 옵션을 사용하여 병렬 빌드를 수행할 때, 의존성 문제가 발생하지 않도록 주의한다.
결론
Makefile은 CI/CD 파이프라인에서의 자동화를 단순화하고 효율을 극대화하는 강력한 도구이다. Makefile의 사용을 통해 일관된 빌드 및 배포 과정을 설정할 수 있으며, 복잡한 명령 시퀀스를 간단히 관리할 수 있다. Makefile을 적절히 활용하면 소프트웨어 및 DevOps 엔지니어링의 생산성을 크게 향상시킬 수 있다.