CI / CD는 이미 백앤드 차원에서는 중요 내용으로 자리 잡은 것으로 보인다.
현재 안드로이드에서 CI / CD의 위상은 어느정도일까?
안드로이드에서 CI / CD 는 부가적인 요소이지만,
왠만한 회사에서는 전부 적용시켜 놓은 듯하다.그리고 안드로이드 채용 우대사항이나 컨퍼런스 발표 주제로도 슬금슬금 나오기도 했다.
이렇게 무관심 영역까진 아니지만
개인적으로 안드로이드에서 CI / CD 를 경험하기 위해서는
얻는 이득보다 소요하는 비용이 컸다.
1. 시간
(CI / CD 툴중 하나인 Jenkins 기준) 실제 서버내에 설치하고 관련 리눅스 설정까지 다 해주어야 한다.
물론 입맛대로 운영하려면 필요한 내용이지만 초보자가 맛보기로 접하기에는 무거운 내용이라 생각한다.
2. 비용
CI / CD 를 장기적으로 운영하고, 테스트 내역도 꾸준히 저장하고 싶을 경우,
마이그레이션하는 시간을 주기적으로 가지거나, 어느정도 운영하다가 기능을 멈추어야 할 수도 있다.
(ex. 아마존 1년 무료 끝, 개인 서버 용량 부족 등)
3. 부가적인 이득
CI / CD 가 없다고 안드로이드 개발이 멈추는 게 아니기 때문에 부가적인 이득이다.
준비할 건 너무 많은데 얻는 건 부가적인 이득이기 때문에,
CI / CD 를 구축할 바에는 안드로이드 개념을 복습하고 실습하는 게 더 낫다.
(그런데 사실 안드로이드 개념이 더 중요하긴 하다 ㅇㅅㅇ)
그런데 야생의 Github Actions 가 튀어나왔다.
위에서 언급했던 고충(?)을 github 에서 알았는지
github actions 라는 경량화된 CI / CD 를 제공해주기 시작했다.
이런 메타(?)에 발맞춰 나도 Github Actions 으로 CI / CD 를 구축해보았는데
시간도 얼마 걸리지 않았고, 기본적인 기능도 구축할 수 있었다.
안드로이드 개발자도 충분히 시간투자해서 접근해볼 수 있는 사안이 된 것 같다는 생각이 들었다.
위에서 말했던 시간, 비용 소모가 줄어들었겠다!
안드로이드 개발자 차원에서 해볼 수 있는 CI / CD 를 정리해보고,
개인적으로 경험했던 내용도 이야기하며 포스트를 단계적으로 작성해보려 한다.
일단 용어 설명부터 해보자면 이렇다
CI : Continuous Integration (지속적 통합)
CD : Continuous Deployment (지속적 배포)
함축적이어서 이해가 잘 안되었다면, 아래 내용을 보면 도움이 될 것이다.
개발 > 빌드 > 테스트 > 배포 과정을 자동화하는 것
"개발 > 빌드 > 테스트" 를 자동화하는 것을 "CI",
"배포" 를 자동화 하는 것을 "CD" 로 보면 된다.
서론에서도 언급했지만 CI / CD 는 없어도 된다.
CI / CD 가 없으면 그냥 개발자가 수동으로 하면 된다.
구축했더라도 기능 개발이 급하거나, 개인 혹은 소규모 프로젝트에서는
CI / CD 의 검증 및 배포 시간을 기다려야 하기 때문에 귀찮게 느껴질 수 있다.
그리고 Android 의 경우 gradle 명령어를 매번 호출하면서
gradle 환경에서만 발생하는 오류를 경험하고 이를 대비하는 코드를 작성하기도 한다.
이런저런 불편함이 있지만 CI / CD 는 아래의 상황에서 진가를 발휘한다.
1. 프로젝트를 담당하는 인원이 많을수록
인원이 많아질수록 코드 변경 정도와 충돌이 심해지고,
검증 또한 그만큼 깊이있게 이루어져야하기 때문에,
각자 작업한 코드가 프로젝트에 정상적으로 반영되는지가 더욱 중요해진다.
2. 프로젝트 규모가 크고 복잡할수록
프로젝트 규모가 클수록 내가 수정한 내용이 다양한 영역에 영향을 끼칠 수 있다.
그리고 프로젝트가 복잡할수록 다양한 환경을 고려할 확률이 높아지고
이에 따라 배포 시 필요한 작업이 많아질 수 있다.
어디서 요긴하게 쓸까?
필요성은 알았는데 그럼 어디서 이 요긴한 시스템을 활용할까?
인원이 많으면서 규모가 제법 있고 복잡한 프로젝트를 운영하는 곳
이 곳은 어디일까?
바로 회사(실무)이다.
실제 채용 공고를 보면 우대사항에 아래의 문구들을 많이 볼 수 있다.
(경력직은 거의 100% 적혀있다.)
자신이 CI / CD 에 대한 경험이나 필요성을 느낀 개발자라면
면접관 분들에게 긍정적인 기운을 보여줄 수 있을 것이라 생각한다.
안드로이드 프로젝트를 여러명이서 협업한다는 가정하에 CI / CD 이야기를 진행해보겠다.
우리는 한 프로젝트를 여러 사람들과 같이하기 위해
그렇게 각자의 작업들이 합쳐졌고, 이제 앱의 배포를 해야한다.
master 브랜치 는 우리가 작업했던 모든 내용이 합쳐진 브랜치이므로
앱을 배포할 때 master 브랜치에서 앱을 추출해 배포해야한다.
그런데 갑자기 누군가가 물어본다.
master 브랜치 에서 추출한 앱.. 문제 없죠?
물어본 사람이 의심이 많다고 생각하고 기분이 나쁠 수 있지만,
문제는 당연히 없어야하는 게 맞다. (당연히 YES 여야 한다)
하지만 안타깝게도 정답은 NO 가 나올때도 있다…
언제 NO 가 될 수 있는지는 아래의 케이스를 통해 확인해보자.
자신이 당장 작업했을 때는 문제 없었더라도
여러명의 작업을 하나에 반영하면서 다양한 변수가 많이 생긴다.
아래는 다양한 변수의 예이다.
master 브랜치, B 작업 브랜치의 conflict 를 수정 반영할 때
ex
A 개발자가 master 브랜치에 반영시킨 내용이,
B 개발자가 작업하던 브랜치에는 반영이 안 되어 있어
작업 내용이 겹쳐 충돌된 코드를 수정하고 master 에 반영하면서
B 개발자의 실수로 A 개발자가 작성했던 코드가 삭제되는 경우
B가 master 브랜치를 pull 한 후, 누락된 작업 내용을 반영할 때
ex
A 개발자가 코드 파일 또는 패키지 정리 작업을 master 브랜치에 반영할 때
B 개발자가 master 최신내용을 작업하던 브랜치에 반영하면서
실수로 자신의 작업을 빼먹거나 추가되어야 할 작업을 놓치는 경우
merge 하면서 변경사항이 꼬여 관련 코드가 누락되는 경우
ex
1, 2 가 겹쳐 master 브랜치로 합치는 과정에서 일부 코드가 없어지는경우
개발자의 실수 혹은 버전 관리 툴을 통해 코드를 합치면서 이런 이슈를 접하게 된다.
특히 Base 부분, 유틸 부분 같이 영향이 큰 코드 작업을 하는 경우 더욱 많이 경험할 확률이 높다.
특히 후자는 위와 같이 사용자에게 치명적인 경험을 선사할수도 있다.
앱 쓰다가 갑자기 앱이 종료되거나 예상과 다른 동작을 하면
그 책임은 온전히 개발자의 책임이자 실책이다.
기존에 로직오류를 대비해 유닛 테스트나 UI 테스트 코드도 작성하며
안전 확인을 하려 노력했다 하더라도, 깜빡하고 실행을 못했다면 책임은 여전히 피할 수 없다.
위와 같은 사례가 정말 있나 궁금한 사람들을 위해 앱 빌드가 되지 않는 사례를 준비했다.
(원래는 스크린샷을 준비했지만 그림도 작고, 파일 이름등도 가려야해서 에러 메시지로 준비했다.)
Only safe (?.) or non-null asserted (!!.) calls are
allowed on a nullable receiver of type RefundPrice?
...
Unresolved reference: calculatedShippingFee
(RefundPrice?
는 타입, calculatedShippingFee
는 변수명 이다.)
master 브랜치 최신 내역을 반영한 후, 충돌된 코드를 고치고 push 했을 때 마주했던 에러이다.
자세히 보면 nullable 로 바뀌면서 발생한 오류, 변수 인식을 못하면서 발생하는 오류들이다.
버전관리 툴 차원에서도 에러를 완벽하게 잡아주지 못한다.
컨벤션 및 코드스타일 규칙을 라이브러리(detekt, ktlint 등)를 활용하여
프로젝트에 적용하고 있다면 작업하면서 스타일 적용을 깜빡할 수 있다.
물론 작업할 때마다 놓치지 않고 확인한다거나,
자동으로 정리해주는 명령어를 활용하면 문제는 없겠지만,
이는 컴파일 오류가 아니기 때문에 놓칠 수 있는 작업이다.
항상 신경쓰라고 하기에는 가혹하지만, 놓친다면 통일성이 깨져 참 그렇다.
위의 안타까운 사연(?)들을 많이 만나 보았는데
만약 내 코드를 master 브랜치에 통합(!)할 때
어떤 시스템이 위 내용들을 자동으로 확인해준다면 어떨까?
새로운 코드 변경 사항이 반영될때마다 빌드 및 테스트 코드를 실행하고,
개발자가 놓칠 수 있는 내용을 인지하고 바로 수정이 가능해질 것이다.
위에서 말한 어떤 시스템이 바로 CI(지속적 통합) 이다.
CI 를 통해 우리는 나름의 안정화된 앱을 추출할 수 있었다. 이젠 배포할 차례다.
최대한 간단하게 작성해보았으며, 무조건 아래 프로세스가 정답은 아니다.
(개발자가 직접 배포하는 시나리오를 가정하여 설명한다.)
1. 배포
구글 플레이스토어로 들어간다.
들어가서 관련 앱 파일, 추가적으로 필요한 파일 (ex. 난독해제 파일 등) 을 업로드하고 배포를 한다.
2. 공유
관련된 내용을 팀원들에게 공유를 한다.
경우에 따라 우리는 작업한 내용 전체를 문서화하여 공유해야 할 수도 있고,
그 앱을 언제든지 다운받을 수 있도록 해야 할 수도 있다.
3. 히스토리 관리 및 다음버전 대비
난 미래를 대비하는 사람이다 (정말?)
이후 히스토리 관리 차원에서 현재 프로젝트 버전을 tag 처리하고
안드로이드에서는 동일한 versionCode 를 가진 앱을 업로드할 수 없으므로
미리 versionCode 를 올려 새로운 배포의 시작을 대비한다.
일부는 선택적인 내용도 있지만 대부분 1. 배포 작업은 반드시 해주어야 한다.
서비스가 발전하면서 점차 개발 환경이 개선되고,
요구 및 개선사항을 느끼며 프로세스가 변경 및 개선될 수 있다.
있을만한 시나리오 3개를 가져와 보았다.
1. 테스트 환경의 앱도 배포하기
이제 우리에게도 테스트 서버가 생겼고, 앞으로 테스트 환경에서의 앱도 같이 확인하고 싶다.
그래서 테스트 서버를 기반으로 하는 앱을 추출하고 따로 업로드해준다.
앱 내에서 환경을 바꾸도록 할 수도 있다.
다만 이렇게 하면 테스트 환경과 프로덕션 환경이 섞이고, 일반 유저의 접근 가능성이 생기게 되므로
이는 개인의 판단에 맡긴다
2. 이전 버전 앱 확인
타 직군에서 이전 버전에서의 앱을 확인하고 싶어한다.
그래서 이전에 tag 했던 버전을 찾아 앱을 추출하고 따로 업로드해준다.
3. 분할 작업
협의 및 개발 스케일 등으로 인해 6개월 ~ 1년 수준의 장기 작업과 유지보수 작업을 같이 병행할 수 있다.
이런 경우 각 상황에 맞는 앱들을 추출하고 (장기 작업 앱, 유지보수용 앱) 따로 업로드 및 배포해준다.
여러가지 상황에 따라 배포 작업에 선택사항들은 계속 붙여지고, 배포 작업은 🏋️ 무거워질 것이다.
배포할때마다 이 작업을 계속 해야하고 고려해주어야 하는 환경개수 만큼 n배의 작업이 될 것이다.
이 작업이 개발실력 성장에 보탬이 된다면 그러려니하겠지만,
이 과정들은 어렵지 않으면서 그냥 해주어야하는 단순 작업들이다.
어느새 배포 작업은 성장에 도움이 되지 않으면서 거대해지고 반복적인 기계 작업이 되어버린다.
(이런 작업을 즐기면서 하라고 하기엔… 명분이 없다.)
그러면서 급한 배포 요청이 들어오는 경우 지금 하고있던 개발을 뒤로 제쳐두고 배포를 먼저 해야 한다.
그리고 사람이기 때문에 배포 도중 실수할 수 있고, 그로 인한 시간과 작업이 추가될 수 있다.
그러다 보면 갑자기 이런 생각(🤦 현타) 가 들게 된다.
🤦🤦🤦 온전한 개발에 집중을 더 하고 싶어요 🤦🤦🤦
이런 저런 사유들로 배포 자체가 개발자에게 스트레스로 다가올 수 있다.
이런 상황에서 소프트웨어를 언제든지 신뢰 가능한 수준으로 배포하고
그 과정을 자동화 하는 이론인 CD (지속적 배포) 는 개발자의 스트레스를 덜어줄 수 있다.
CD 를 통해 배포를 자동화하여 우리는 원활히 배포하고
여러 단순 작업들을 자동화하여 개발자에게 많은 편의를 제공할 수 있다.
물론 위와 같이 가위로 한땀한땀 깎는 게 정확도를 높일 수 있겠지만
저게 메인 작업이 아니라면 자동화 도구 (제초기 등) 을 사용해서 시간을 더는 게 좋을 것이라 생각한다.
Q. 그런데 CD 는 프로덕션 배포를 고려하는 이론 아닌가요?
맞는 말이다. 사실 CD의 원론적 의미 자체는 변경 사항을 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스 하는 것을 이야기한다.
하지만 우리는 개발자 및 팀의 편의를 위해 추가적인 작업을 해주고 (2. 이전 버전 앱 확인),
다른 환경으로의 자동 릴리스를 고려하는 것(1. 테스트 환경의 앱도 배포하기)도 확인할 수 있었다.CD 는 프로덕션 환경 뿐만 아니라 다양한 환경에 대한 시나리오도 자동화하여 모두에게 편안함을 줄 수 있다.
쓰다가 내용이 너무 길어져 일단은 개념만 언급하고 마무리 하려한다.
위에서 github actions 이 경량화된 CI 라고 했는데 실제 어느정도까지 가벼워졌는지
github actions 을 실제 사용해보면서 직접 느껴보는 절차(?)를 밟아보도록 하자