Outsider's Dev Story: Infrastructure 카테고리 글 목록https://blog.outsider.ne.kr/Stay Hungry. Stay Foolish. Don't Be Satisfied.2024-03-15T10:03:30+09:00Textcube 1.10.7 : Tempo primoAWSKRUG 플랫폼엔지니어링 모임에서 발표한 "당근 개발자 플랫폼은 어떤 문제를 해결하고 있는가?"Outsiderhttps://blog.outsider.ne.kr/17032024-01-11T02:24:06+09:002024-01-11T02:24:06+09:00<p>AWSKRUG 플랫폼엔지니어링 모임에서 "당근 개발자 플랫폼은 어떤 문제를 해결하고 있는가?"라는 제목으로 발표했다. 현재 당근마켓 SRE팀의 딜리버리 파트에서 배포 시스템을 시작으로 해서 사내 개발자 플랫폼(IDP, Internal Developer Platform)을 만들고 있다.</p>
<p>플랫폼 엔지니어링을 목표로 했다기 보다는 배포 시스템을 만들고 이를 플랫폼으로 발전시키면서 고민하다 보니 어느 순간 <a href="https://platformengineering.org/">플랫폼 엔지니어링</a>이라는 개념을 만나게 되었다. 사실 플랫폼 엔지니어링은 지난 십여 년간 IT를 이끈 빅테크 기업인 Google이나 Netflix 등에서 적용했던 방법이 이제 업계로 나오고 있는 거라고 생각하고 있다.</p>
<p>플랫폼 엔지니어링이란 개념을 너무 팔면 약 파는 기분도 들고 해서 조심하긴 했지만 그래도 관련한 일을 계속하면서 고민하고 있다 보니 하는 일에 관해서 지난 3년간 여러 번의 발표를 했다.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=pc3H9i6Yv2Y">2021년 1월 17일 당근 SRE 밋업 2회 - 당근마켓은 왜 배포 시스템을 만드는가?</a></li>
<li><a href="https://blog.outsider.ne.kr/1635">2022년 11월 22일 Google Cloud Next Innovators Hive - 클라우드 시대에 맞는 사이트 신뢰성 엔지니어</a></li>
<li><a href="https://www.youtube.com/watch?v=JWR8pEPimsY">2023년 6월 15일 당근 SRE 밋업 3회- 당근마켓 개발자 플랫폼: 지난 2년간 무엇을 만들었는가?</a></li>
<li><a href="https://blog.outsider.ne.kr/1684">2023년 8월 15일 Infcon - DevOps를 가속화하는 플랫폼 엔지니어링</a></li>
</ul>
<p>플랫폼 엔지니어링 관련 글은 웬만한 건 다 찾아봤다고 생각하고 있지만 또 혼자 공부하면서 이해하고 해석했기에 지난 11월 AWSKURG에서 <a href="https://www.meetup.com/awskrug/events/297065221/">플랫폼엔지니어링모임</a>이 생겼길래 참석했다. 팀 내에서는 여러 번 얘기하고 했지만, 내가 해석하고 이해한 거였기 때문에 다른 회사, 다른 사람들은 플랫폼 엔지니어링을 어떻게 보고 있는지 궁금해서 참석했다. 그렇게 참석했다가 그 자리에서 다음 밋업의 발표를 제안받고 발표를 하게 되었다.</p>
<p>다른 콘퍼런스나 세미나보다는 점 격식 없으면서 시간 여유도 있는 편이라 이전에 못 했던 얘기를 할 수 있을 거로 생각했다. 그동안의 발표는 제한된 시간에 주제에 맞게 전달하려다 보니 개념적으로 정리해서 얘기한 게 많았다. 결국 같은 얘기이긴 하지만 저번 밋업에서 끝나고 질문을 받으면서 지난 3년간 사내 개발자 플랫폼을 만들면서 어떤 상황에서 어떤 고민을 하면서 만들었는지는 좀 더 그대로 이야기하는 것도 도움이 될 것 같다고 생각했다.</p>
<p>결국 발표이긴 하니까 어느 정도 정리가 필요하기는 하지만 넉넉한 시간을 이용해서 그때의 상황과 어떤 고민을 하고 어떻게 플랫폼을 만들면서 발전해 왔는지를 공유하고 싶었다. 그렇게 발표 자료를 만들다 보니 장표가 113 페이지나 나왔다. 그래도 실제로 했던 얘기를 풀어나가는 거라서 이전에 했던 다른 발표보다 크게 힘들거나 하진 않았고 오히려 다시 정리하다 보니 "그때 그런 생각을 했었지", "맞다. 그때 그랬었지"하는 생각도 나서 재밌었다.</p>
<script defer class="speakerdeck-embed" data-id="e78d249d77dc4bf3846e96789f0a130e" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>
<p>장표가 113페이지가 되니 분량이 잘 가늠이 안 되어서 평소와 달리 발표 연습은 하지 않았다. 발표 시간이 명확하게 정해져 있진 않았고.. 그래도 평소의 발표 경험이 있다 보니 90분이 넘진 않겠느냐고 했는데 실제로 하니까 쉬는 시간 포함해서 딱 90분에 발표가 끝났다.(화면 연결을 제대로 준비하지 못해서 발표자 노트가 내 맥북에 나오지 않아서 열심히 적어놓은 내용을 기억으로만 발표해야 해서 좀 아쉬웠다. ㅠ) 끝나고 질문도 많이 나와서 질문/답변만 30분을 했다. ㅎㅎ</p>
<p>이전에도 발표하면서 참석자들이 꽤 있었지만 그래도 아직 플랫폼 엔지니어링은 아직 마이너한 주제라고 생각하고 있었다.(물론 난 인프라팀의 방향에서 이 방향이 맞는다고 생각하고 있다.) AWSKRUG에서 발표하는 건 처음인데 아무래도 인프라에 관심 있는 분들이 많아서 그런지 밋업치고는 상당히 많은 210명 정도가 참석해 주셔서 "어라? 플랫폼 엔지니어링에 관심이 언제 이렇게 커졌지?"하는 생각마저 들었다.</p>
<p>90분 발표란 게 듣는 입장에서 쉽지 않은데 많은 분이 그래도 재밌게 들어주신 거 같아서 준비하는 과정이나 발표나 꽤 즐거운 시간이었다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1703?commentInput=true#entry1703WriteComment">댓글 쓰기</a></strong></p>1Password의 Service Accounts로 시크릿 관리하기Outsiderhttps://blog.outsider.ne.kr/17002023-12-28T00:38:51+09:002023-12-28T00:38:51+09:00<p>비밀번호 관리 서비스인 <a href="https://1password.com">1Password</a>는 2022년에 <a href="https://1password.community/discussion/127893/introducing-1password-developer-tools">1Password Developer Tools</a>를 런칭하고 <a href="https://blog.1password.com/1password-ssh-agent/">SSH와 Git의 비밀키 관리 기능</a>도 추가하면서 사용자의 비밀번호 관리뿐만 아니라 시스템이나 개발자의 보안을 유지할 수 있는 기능도 추가하고 있다.</p>
<p>작년인가 1Password의 <a href="https://developer.1password.com/docs/connect">Connect Server</a>를 보고 이를 이용해서 서비스에서 시크릿 보관소로 쓸 수 있을지 궁금했던 기억이 있다. 테스트해 봐야지 하고는 아직 못하고 있었다.</p>
<p>최근 GitHub Universe에서 1Password 부스를 구경하다가 <a href="https://1password.com/developers">1Password Developer Tools</a>에 Service Accounts라는 기능이 나왔다는 것을 알게 되었고 내가 올해 고민하던 문제의 해결책이 될 수 있겠다는 생각에 살펴봤다.</p>
<p>내가 생각하는 문제는 보통 로컬에서 개발 환경을 구성하기 위해서 다양한 시크릿 정보를 환경 변수에 저장해 놓고 사용하게 되고 <a href="https://blog.outsider.ne.kr/1306">direnv</a>나 그 외 환경변수 관리 도구를 쓴다고 하더라도 일반적으로 이 정보는 로컬에 보통 평문으로 저장되어 있다. 개발 환경이긴 하지만 상황에 따라선 프로덕션용 시크릿이 보관되어 있을 수 있다. 물론 컴퓨터의 자체 보안이 있고 노트북을 잃어버려도 보통 암호가 걸려있어서 암호가 풀린 상태로 다른 사람에게 노트북을 주거나 공격자에게 침투당하는 상황까지 고려하는 것은 아니지만 파일을 복사하거나 백업하면서 실수로라도 이러한 시크릿이 유출될 가능성이 높다는 것이었다.<br />
<br></p>
<h1>1Password Service Accounts</h1>
<p>1Password Service Accounts는 올 <a href="https://blog.1password.com/1password-service-accounts/">6월에 퍼블릭 베타로 공개</a>된 기능이고 지금은 베타가 끝나서 공개된 상태이다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/5585988691.jpg" width="750" height="430" alt="사용자 삽입 이미지" title="" /></p>
<p><a href="https://my.1password.com/developer-tools/directory">1Password의 Developer Tools</a>에 들어가면 CI, Kubernetes, CLI, SSH/Git, IDE와 통합할 수 있는 메뉴가 나온다. 참고로 IDE는 현재는 VS Code만 통합할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/3624803287.jpg" width="750" height="436" alt="사용자 삽입 이미지" title="" /></p>
<p>GitHub Actions를 클릭해서 Service Accounts와 Connect Server 중에서 선택할 수 있게 나온다. 둘 다 시크릿은 자동화해서 연동할 수 있게 하는 기능인데 Service Accounts는 CLI를 사용해서 연동할 수 있고 Connect Server는 별도의 서버를 실행해서 연동할 수 있다. Connect Server는 나중에 또 공부해 보려고 하는 데<a href="https://developer.1password.com/docs/secrets-automation">둘의 기능 차이</a>를 살펴보면 다음과 같다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4161871076.jpg" width="500" height="501" alt="사용자 삽입 이미지" title="" /></p>
<p>CI나 로컬에서 연동하려면 별도의 서버 관리가 필요 없는 Service Accounts가 더 적합해 보였다.</p>
<p>Service Accounts 생성을 클릭하면 이름을 입력할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/2767358670.jpg" width="750" height="493" alt="사용자 삽입 이미지" title="" /></p>
<p>생성할 서비스 어카운트가 접근할 금고(Vault)를 선택할 수 있다. 권한은 읽기만 허용하거나 읽기/쓰기까지 가능하거나 할 수 있다. 서비스 어카운트에서는 주로 읽기를 할거라고 생각했기에 읽기 권한만 부여했다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/8275199701.jpg" width="750" height="494" alt="사용자 삽입 이미지" title="" /></p>
<p>생성이 완료되면 해당 서비스 어카운트의 인증 토큰이 나오고 이 토큰은 <a href="https://developer.1password.com/docs/cli">1Password CLI</a>에서 사용할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/2789847765.jpg" width="750" height="522" alt="사용자 삽입 이미지" title="" /></p>
<p>이 토큰을 나중에 사용해야 하므로 1Password에 저장해 둘 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/6796011955.jpg" width="400" height="603" alt="사용자 삽입 이미지" title="" /></p>
<h1>1Password CLI</h1>
<p>macOS 기준으로 <a href="https://brew.sh/">Homebrew</a>를 이용해서 <a href="https://developer.1password.com/docs/cli/get-started/">설치</a>하거나 <a href="https://app-updates.agilebits.com/product_history/CLI2">직접 다운로드</a> 받아서 사용할 수 있다.</p>
<p>CLI는 <code>op</code> 라는 명령어로 사용할 수 있고 현재 최신 버전은 <code>2.24.0</code>이다.</p>
<pre class="line-numbers"><code class="language-bash">$ op -v
2.24.0
</code></pre>
<p>인증을 하기 위해 <code>OP_SERVICE_ACCOUNT_TOKEN</code> 환경변수에 아까 발급받은 인증 토큰을 지정한다.</p>
<pre class="line-numbers"><code class="language-bash">$ export OP_SERVICE_ACCOUNT_TOKEN=ops_eyJ...
</code></pre>
<p>그리고 1Password 앱의 설정의 개발자 섹션에서 "1Password CLI와 통합"을 활성화해야 한다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/8602273020.jpg" width="600" height="430" alt="사용자 삽입 이미지" title="" /></p>
<p>이를 활성화하면 1Password의 각 시크릿에서 "보기"와 "크게 보기" 외에도 "비밀 참조 복사"라는 항목이 생기게 된다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/7663474158.jpg" width="750" height="271" alt="사용자 삽입 이미지" title="" /></p>
<p>위는 예시로 API 키를 저장한 시크릿인데 이 "비밀 참조 복사"를 하면 <code>op://Dev/demo/api_key</code>와 같은 주소가 생긴다. 여기서 <code>op://[금고 이름]/[시크릿 이름]/[필드 이름]</code> 형태가 된다. 여기서 각 항목에 <a href="https://developer.1password.com/docs/cli/secrets-reference-syntax/#syntax-rules">지원하지 않는 문자</a>가 있는 경우에는 <code>op://Dev/iunplqsduyobjbri45irjajgcu/password</code>처럼 이름 대신 UID가 생성된다.</p>
<p>앞에서 서비스 어카운트를 만들고 발급받은 인증 토큰을 <code>OP_SERVICE_ACCOUNT_TOKEN</code> 환경변수에 저장하면 바로 CLI를 사용할 수 있다.</p>
<p><code>op vault list</code> 명령어로 금고 목록을 볼 수 있는데 해당 금고는 <code>Dev</code> 금고에만 접근 권한을 주었기 때문에 한 개만 나온다.</p>
<pre class="line-numbers"><code class="language-bash">$ op vault list
ID NAME
u7mujejocgwhnu3sxbxjehtdpm Dev
</code></pre>
<p>CLI에서 각 아이템의 목록을 볼 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ op item list
ID TITLE VAULT EDITED
4phd2vr5vnqt3ssrjpupdfbkfy demo Dev 10 minutes ago
bkgfwcb25abmp4wwslo4ss2qwy Service Account Auth Token: GitHub Actions Dev 2 weeks ago
</code></pre>
<p>1Password 앱을 켜지 않고도 특정 아이템의 자세한 내용을 확인해 볼 수 있다.(여기서 보이는 <code>api_key</code>는 임의로 만든 문자열이다.)</p>
<pre class="line-numbers"><code class="language-bash">$ op item get demo --vault Dev
ID: 4phd2vr5vnqt3ssrjpupdfbkfy
Title: demo
Vault: Dev (u7mujejocgwhnu3sxbxjehtdpm)
Created: 1 day ago
Updated: 10 minutes ago by Outsider
Favorite: false
Version: 3
Category: SERVER
Fields:
api_key: RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j
관리자 콘솔:
호스팅 제공업체:
</code></pre>
<p>CLI에서 비밀 참조를 알고 싶다면 JSON 형식으로 출력해 보면 확인할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ op item get demo --vault Dev --format json
{
"id": "4phd2vr5vnqt3ssrjpupdfbkfy",
"title": "demo",
"version": 3,
"vault": {
"id": "u7mujejocgwhnu3sxbxjehtdpm",
"name": "Dev"
},
"category": "SERVER",
"last_edited_by": "K2ACBLCXENBKJLN2V6Y3HPRSBY",
"created_at": "2023-12-19T08:10:17Z",
"updated_at": "2023-12-21T01:48:25Z",
"sections": [
{
"id": "hosting_provider_details",
"label": "호스팅 제공업체"
}
],
"fields": [
{
"id": "password",
"type": "CONCEALED",
"label": "api_key",
"value": "RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j",
"reference": "op://Dev/demo/api_key"
},
{
"id": "name",
"section": {
"id": "hosting_provider_details",
"label": "호스팅 제공업체"
},
"type": "STRING",
"label": "이름",
"reference": "op://Dev/demo/hosting_provider_details/name"
}
]
}
</code></pre>
<p><br></p>
<h2><code>op read</code></h2>
<p><code>op read</code>는 비밀 참조의 값을 읽어오는 명령어로 다음과 같이 비밀번호를 조회해 볼 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ op read op://Dev/demo/api_key
RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j
</code></pre>
<p>이를 이용해서 특정 시크릿 값을 파일에 저장할 수도 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ op read op://Dev/demo/api_key --out-file key.txt
/Users/outsider/temp/op-test/key.txt
$ cat key.txt
RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j
</code></pre>
<p>값을 확인하고자 할 때는 <code>op read</code>를 이용할 수 있지만 서비스 어카운트를 쓴다는 것 자체가 비밀번호를 직접 다루지 않기 위함이라고 생각하기 때문에 이 명령어는 CLI에 약간 익숙해 지면 쓸 일은 없어질거로 생각한다.<br />
<br></p>
<h2><code>op run</code></h2>
<p>아마 서비스 어카운트에서 가장 많이 사용할 명령어라고 생각한다. 1Password 금고에 시크릿을 저장했다면 개발할 때 이를 가져다 써야 하는데 <code>op run</code>이 이 문제를 해결하는 명령어다.</p>
<pre class="line-numbers"><code class="language-bash">$ export API_KEY=op://Dev/demo/api_key
</code></pre>
<p>위와 같이 내가 원하는 환경변수(<code>API_KEY</code>)를 앞의 비밀 참조 값으로 설정한다.</p>
<p>애플리케이션에서 이 환경변수를 사용하는 상황을 테스트하기 위해 아래와같이 간단한 Node.js 코드를 작성했다. 3000 포트에 떠 있는 다른 서버에 <code>API_KEY</code> 환경 변수를 쿼리스트링으로 전달하고 <code>API_KEY</code>을 로그로 출력하고 응답받은 결과를 출력하도록 했다.</p>
<pre class="line-numbers"><code class="language-js">// app.js
fetch(`http://localhost:3000?secret=${process.env.API_KEY}`)
.then(async (res) => {
console.log(`API_KEY: ${process.env.API_KEY}`);
const result = await res.text();
console.log(result);
})
</code></pre>
<p>당연하게도 <code>API_KEY</code>의 값으로 <code>op://Dev/demo/api_key</code>가 출력되고 다른 서버의 로그에도 <code>op://Dev/demo/api_key</code>가 출력된다.</p>
<pre class="line-numbers"><code class="language-bash">$ node app.js
API_KEY: op://Dev/demo/api_key
Response from another server
</code></pre>
<p>이를 <code>op run --</code> 명령어와 연결해서 서버를 실행하면 환경변수 중에 존재하는 비밀 참조를 모두 찾아서 1Password 값으로 치환해서 처리해 준다.(참고로 <code>--</code>인 더블 대시는 셸에서 옵션과 인자를 구분하는 문법이다.)</p>
<pre class="line-numbers"><code class="language-bash">$ op run -- node app.js
API_KEY: <concealed by 1Password>
Response from another server
````
위에서 보듯이 해당 환경변수를 로그에 출력했을 때도 값이 출력되는 게 아니라 `<concealed by 1Password>`로 가려진다. 당연히 다른 서버에 전달된 쿼리스트링에는 실제 시크릿 값이 제대로 전달된다.
이렇게 하면 **로컬에서 환경변수를 관리하면서도 실제 시크릿 값을 가지고 있지 않을 수 있다. 실행명령어에 `op run`이 붙어야 하는 불편함이 있지만 보안상으로도 좋고 환경변수 파일을 그대로 GitHub에 공유한다고 하더라도 실제 시크릿 값은 포함되어 있지 않기 때문에 `.env.example` 같은 걸 만들 필요 없이 그냥 환경 파일을 공유해서 사용하는 것도 가능하고 다 참조 값이기 때문에 해당 값을 바꿀 때도 1Password에서 바꾸면 바로 로테이션시킬 수 있다.**
하지만 1Password API에 찔러서 가져오는 것이기 때문에 항상 인터넷에 연결되어 있어야 하고 당연히 API 값을 가져오느라 약간의 시간이 더 걸린다.
```bash
$ op run -- printenv API_KEY
<concealed by 1Password>
$ op run --no-masking -- printenv API_KEY
RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j
</code></pre>
<p>환경변수 값을 확인하고 싶을 때는 위와 같이 확인해 볼 수 있고 <code>--env-file</code> 옵션을 사용하면 사용하고자 하는 환경파일도 지정할 수 있다.<br />
<br></p>
<h2><code>op inject</code></h2>
<p>셸 스크립트 등에서 비밀 참조를 변환해서 실행하고 싶다면 다음과 같이 <code>op inject</code>를 파이프로 연결하면 전달받은 문자열의 비밀 참조를 실제 값으로 치환한다.</p>
<pre class="line-numbers"><code class="language-bash">$ echo "API key is op://Dev/demo/api_key" | op inject
API key is RvN4HNRk2oE.iL*42veP.UE.eDre6rPf_K*@m8!j
</code></pre>
<p>여기서 비밀 참조인 <code>op://Dev/demo/api_key</code>를 처리할 때 환경변수도 섞어서 사용할 수 있다. 예를 들어 다음과 같이 같은 API 키가 DEV, Alpha, Prod 3개가 있을 때 같은 패턴으로 만들면 <code>op://$ENV/demo/api_key</code>로 환경변수에 지정하고 <code>ENV</code> 환경변수로 참조할 시크릿을 바꿔가면서 쓸 수 있다.</p>
<ul>
<li><code>op://Dev/demo/api_key</code></li>
<li><code>op://Alpha/demo/api_key</code></li>
<li><code>op://Prod/demo/api_key</code><br />
<br></li>
</ul>
<h1>GitHub Actions에서 1Password Service Accounts</h1>
<p>로컬에서 사용하는 방법을 알아봤으니 당연히 CI에서도 사용할 수 있다. CI에서는 GitHub 기준으로 저장소에 액션 시크릿을 설정할 수 있고 공통으로 사용하는 시크릿은 Org에 시크릿을 설정해서 공통으로 사용할 수 있다. 기본적으로 GitHub에서는 시크릿을 설정한 뒤에는 내용을 확인하기 어렵기 때문에 개인이라면 좀 낫지만, 회사 차원에서는 예전에 어떤 값을 설정했는지 확인하기 어려워서 로테이션시키기가 어려운데 이때도 1Password Service Accounts를 쓸 수 있다.</p>
<p>1Password에 접근하기 위해 저장소에 <code>OP_SERVICE_ACCOUNT_TOKEN</code>을 저장한다. 여기서는 데모라서 저장소에 Actions 시크릿으로 저장했지만, 회사라면 Org 시크릿으로 저장하거나 관리 정책에 따라 팀별로 따로 지정하는 등의 방법이 가능하다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4954728147.jpg" width="750" height="433" alt="사용자 삽입 이미지" title="" /></p>
<p>GitHub Actions에 다음과 같은 액션을 만들어 보자. 여기서는 <a href="https://github.com/1password/load-secrets-action">1password/load-secrets-action</a> 액션을 사용해서 시크릿을 불러와서 원하는 환경변수에 저장할 수 있다. <code>export-env</code>를 <code>true</code>로 설정해야 다음 스텝에서도 해당 환경변수를 사용할 수 있다. 참고로 <code>on</code>에 <code>workflow_dispatch</code>로 지정한 것은 GitHub UI에서 수동으로 실행해서 테스트하기 위함이고 <code>Print unmasked secret</code> 스텝은 보통은 하면 안 되는 트릭이지만 GitHub Actions에서 <a href="https://stackoverflow.com/a/72881741">시크릿 로깅을 허용하지 않기 때문에 이를 회피해서 값을 확인하기 위한 우회법</a>이다.</p>
<pre class="line-numbers"><code class="language-yaml">name: 1Password test
on: workflow_dispatch
jobs:
1pw-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Load secret
uses: 1password/load-secrets-action@v1
with:
# Export loaded secrets as environment variables
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
API_KEY: "op://Dev/mysql/password"
- name: Print masked secret
run: echo "Secret is $API_KEY"
- name: Print unmasked secret
run: echo "$API_KEY" | sed 's/./& /g'
</code></pre>
<p>이 액션을 실행해 보면 다음과 같이 1Password에서 시크릿 값을 가져와서 사용할 수 있는 것을 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/1887521362.jpg" width="750" height="393" alt="사용자 삽입 이미지" title="" /></p>
<p>이렇게 사용하면 로컬이나 CI에서 1Password의 서비스 어카운트 토큰만 저장하고 다른 시크릿은 모두 1Password에서 관리할 수 있는 구조로 만들 수 있다. 보는 관점에 따라서 1Password에 다 담아 놓는 게 좋은가 아닌가는 의견이 갈릴 수 있지만 나는 유출된 지점을 줄이면서 로테이션시킬 수 있다는 점에서 긍정적으로 보고 있다.</p>
<p>물론 1Password의 보안은 상당히 신뢰하는 편이지만 1Password API서버에게 장애가 생기면서 로컬이나 CI에도 영향을 받기 때문에 이 부분은 같이 고려해야 할 것으로 생각한다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1700?commentInput=true#entry1700WriteComment">댓글 쓰기</a></strong></p>[Book] Observability EngineeringOutsiderhttps://blog.outsider.ne.kr/16882023-10-14T23:16:31+09:002023-10-14T23:16:18+09:00<div>
<fieldset style="padding: 20px 5px 5px 5px;">
<legend><a href="https://www.oreilly.com/library/view/observability-engineering/9781492076438/">Observability Engineering</a></legend>
<table>
<tbody>
<tr>
<td>
<a href="https://www.oreilly.com/library/view/observability-engineering/9781492076438/"><img src="//blog.outsider.ne.kr/attach/1/5492955517.jpg" alt="책 표지" title="" /></a>
</td>
<td style="vertical-align: top">
<a href="https://www.oreilly.com/library/view/observability-engineering/9781492076438/">Observability Engineering</a> - ⭐⭐⭐⭐
<br>Charity Majors, Liz Fong-Jones, George Miranda 지음<br>O'Reilly Media
</td>
</tr>
</tbody>
</table>
</fieldset>
<br>
</div>
<p>수년째 참여하고 있는 인프라 스터디에서 그룹스터디하면서 읽은 책이다. 이 책은 트위터에서 <a href="https://twitter.com/dylayed">Daniel Lee님</a>이 추천해 줘서 위시리스트에 담고 있었는데 스터디 주제를 찾다가 이 책을 선정했다. 아무래도 원서라서 은근히 미루게 되어 안 읽게 되는데 그룹스터디로 하면 끝까지 읽을 수 있다.</p>
<p>5월 23일에 스터디를 시작해서 매주 하면서 이번 주까지 했으니 거의 6개월 스터디를 했다. 스터디는 좋았지만 그래도 6개월은 꽤 길긴 하다. 항상 더 빨리 끝내고 싶지만, 또 스터디를 하다 보면 진도 빼는 게 목적이 아니라 공부하는 게 목적이다 보니 이야기 나누고 질문하고 하다 보면 길어지기도 하고 해서 항상 5~6개월은 하게 되는 거 같다. 그래도 이제는 <a href="https://www.deepl.com/">DeepL</a>이 있어서 이전보다 훨씬 수월하게 원서로 스터디를 진행했다. 전에도 좀 이용하긴 했지만 아무래도 번역 품질이 좋아져서 후처리를 좀 덜 해도 되기도 하고 후처리를 안 해도 한국말은 좀 어색하지만, 대부분의 경우 무슨 말인지 이해는 할 수 있었다.</p>
<p>그리고 <a href="https://newrelic.com/">New Relic</a>이나 <a href="https://www.datadoghq.com/">Datadog</a>등을 써보긴 했지만 옵저버빌리티 혹은 모니터링 시스템을 구축하는 역할은 아니라서 아주 자세히는 알지 못한다. PromQL도 잘 못 쓰는 사용자 정도의 느낌이지만 인프라 업무를 하다 보니 어느 정도의 관심은 있고 주워들은 것도 있는데 같이 스터디하시는 분들이 다양한 경험들이 있어서 그룹 스터디를 한 게 혼자 읽는 것 보다 더 도움이 됐다.<br />
<br><br></p>
<p>이 책은 옵저버빌리티 솔루션인 <a href="https://www.honeycomb.io/">honeycomb.io</a>의 Charity Majors, Liz Fong-Jones, George Miranda 세 명이 쓴 책으로 옵저버빌리티 솔루션을 만들면서 그동안 했던 많은 고민의 결과를 담은게 이 책이라고 생각한다.</p>
<p>Charity Majors는 honeycomb.io의 공동창업자이기도 한데 책에도 나오지만 2011년에 모바일 개발자에게 백엔드를 제공해 주는 Backend as a Service(BaaS)로 출시된 <a href="https://en.wikipedia.org/wiki/Parse,_Inc.">Parse</a>에서 일했는데 Parse 특성상 한 사용자가 갑자기 서버 리소스를 다 먹거나 문제를 일으키거나 할 때 문제를 파악하면서 옵저버빌리티에 관심을 가지게 되었고 Parse가 Facebook에 인수되어 Facebook에서 일하면서 거의 모든 데이터를 다 넣어놓고 탐색하는 <a href="https://research.facebook.com/publications/scuba-diving-into-data-at-facebook/">Scuba</a>를 쓰면서 옵저버빌리티에 대한 많은 생각을 가지게 되고 결국 창업까지 이어지고 이 책까지 나오게 된다.</p>
<p>아무래도 모니터링이나 옵저버빌리티를 고민하고 있거나 구축하는 사람들에게 도움이 될 책이라고 생각한다. 어떤 면에서는 너무 이상적인 얘기를 한다는 느낌이 들 수도 있지만 앞으로 우리가 준비해야 할 옵저버빌리티는 이래야 한다고 방향성을 제시한다는 점에서 꽤 좋았고 저자들이 많은 경험이 있기 때문에 기술적으로 고려해야 할 부분도 잘 짚어주고 있어서 도움이 많이 되었다.<br />
<br></p>
<h2>Part 1. The Path to Observability</h2>
<blockquote>
<p>데이터베이스의 맥락에서 카디널리티는 집합에 포함된 데이터 값의 고유성을 나타냅니다. 낮은 카디널리티는 열의 집합에 중복된 값이 많다는 것을 의미합니다. 높은 카디널리티는 열에 완전히 고유한 값이 많이 포함되어 있음을 의미합니다. 단일 값을 포함하는 열은 항상 가능한 가장 낮은 카디널리티를 갖습니다. 고유 ID를 포함하는 열은 항상 가능한 가장 높은 카디널리티를 갖습니다.</p>
</blockquote>
<blockquote>
<p>카디널리티는 옵저버빌리티에 중요한데, 카디널리티가 높은 정보는 거의 항상 시스템을 디버깅하거나 이해하기 위해 데이터를 식별하는 데 가장 유용하기 때문입니다.</p>
</blockquote>
<blockquote>
<p>안타깝게도 메트릭 기반 툴링 시스템은 카디널리티가 낮은 차원만 합리적인 규모로 처리할 수 있습니다. 비교할 호스트가 수백 개에 불과하더라도 메트릭 기반 시스템에서는 카디널리티 키 공간의 한계에 부딪히지 않고는 호스트 이름을 식별 태그로 사용할 수 없습니다.</p>
</blockquote>
<blockquote>
<p>옵저버빌리티란 새로운 코드를 배포하지 않고도 시스템이 아무리 새롭거나 기괴한 상태에 빠질 수 있는 모든 상태를 이해하고 설명할 수 있다는 것을 의미합니다.</p>
</blockquote>
<p>이 책 전체적으로 높은 카디널리티를 엄청나게 강조하고 있다. 지금 사용하는 모니터링 시스템(이 책에서는 모니터링과 옵저버빌리티를 구분한다)에서는 카니널리티가 낮기 때문에 더 자세하게 파악하기가 어려운데 카디널리티를 높게 할 수 있으면 시스템의 더 자세한 내용을 관측할 수 있다.</p>
<blockquote>
<p>기존 모니터링 도구는 이전에 알려진 오류 조건의 존재 여부를 나타내는 알려진 임곗값(threshold)에 대해 시스템 조건을 확인하는 방식으로 작동합니다. 이는 이전에 발생한 장애 모드를 식별하는 데만 효과적이기 때문에 근본적으로 사후 대응적인 접근 방식입니다.<br />
반면, 옵저버빌리티 도구는 반복적인 탐색 조사를 통해 성능 문제가 발생할 수 있는 위치와 이유를 체계적으로 파악할 수 있습니다. 옵저버빌리티는 이전에 알려졌거나 알려지지 않은 모든 장애 모드를 식별하기 위한 사전 예방적 접근 방식을 가능하게 합니다.</p>
</blockquote>
<blockquote>
<p>메트릭은 일반적으로 종합적으로 볼 때 더 유용합니다. 추세 이해하기 메트릭 값을 이해하면 소프트웨어 성능에 영향을 미치는 시스템 동작에 대한 인사이트를 얻을 수 있습니다. 모니터링 시스템은 메트릭을 수집, 집계 및 분석하여 사람이 알고 싶어하는 추세를 나타내는 알려진 패턴을 선별합니다.</p>
</blockquote>
<p>기존 모니터링과 옵저버빌리티를 구분해서 얘기하고 있는데 옵저버빌리티를 그래서 어떻게 이렇게 할 수 있느냐를 떠나서 모노리스로 서버 한 대가 있다면 큰 상관이 없겠지만 마이크로서비스로 서비스가 많이 떠 있다면 모니터링 자체가 상당히 어려워지기 때문에 이 내용에 동의하는 편이다. 오류가 증가하거나 레이턴시가 증가할 때 연쇄적으로 발생할 가능성이 높기 때문에 어디가 원인이고 어디가 영향받은 것인지 파악하는 것도 쉽지 않은 일이다.</p>
<blockquote>
<p>그 임곗값이 정확히 어디인지는 사람이 결정합니다.</p>
</blockquote>
<blockquote>
<p>모니터링 기반 접근 방식에서는 팀에서 가장 오래 근무한 엔지니어가 팀에서 가장 뛰어난 디버거이자 최후의 디버거인 경우가 많기 때문에 연공서열이 지식의 핵심이라는 생각에 초점을 맞추는 경우가 많습니다</p>
</blockquote>
<blockquote>
<p>옵저버빌리티 도구를 사용하면 팀에서 가장 뛰어난 디버거는 일반적으로 가장 호기심이 많은 엔지니어입니다. 옵저버빌리티를 실천하는 엔지니어는 탐색적인 질문을 통해 시스템을 조사하고, 발견한 답을 사용하여 더 개방적인 질문을 할 수 있는 능력을 갖추고 있습니다</p>
</blockquote>
<blockquote>
<p>프로덕션 환경에서 알려진 임곗값을 초과하는 시스템 상태의 엣지 케이스를 찾는 모니터링 알림은 엄청난 수의 오탐, 오탐, 무의미한 노이즈를 생성합니다. 알림은 사용자 경험에 직접적인 영향을 미치는 증상에만 집중하여 더 적은 수의 알림을 트리거하는 모델로 전환되었습니다.</p>
</blockquote>
<p>매트릭의 임곗값 설정도 메인 서비스 한두 개로 운영한다면 계속 운영하면서 조정하면 가능하지만 수십 개 수백 개가 된다면 이마저도 쉽지 않은 일이다.</p>
<blockquote>
<p>수동으로 해결해야 하고 런북에 정의할 수 있는 알려진 반복 장애는 더 이상 일반적이지 않습니다. 서비스 장애는 이러한 모델에서 알려진 반복 장애를 자동으로 복구할 수 있는 모델로 전환되었습니다. 자동으로 복구할 수 없어 알림이 트리거되는 장애는 대응하는 엔지니어가 새로운 문제에 직면하게 될 가능성이 높습니다.</p>
</blockquote>
<p>최근에 많이 생각하는 일이긴 하다. 장애 대응을 문서로 만들 수 있다면 보통 자동화 처리하는 게 편하고(대표적으로 Kubernetes에서 컨테이너가 죽으면 리스타트하는 걸 들 수 있다) 결국 장애에서 문서화를 한다는 것은 사람이 판단할 수 있는 정보를 넣어야 하는데 이런 걸 결국 문서화가 어렵다. 장애 대응 문서를 그대로 따라 하면 해결이 된다면 굳이 사람이 할 필요가 있는가?</p>
<blockquote>
<p>기존 접근 방식의 한계는 무엇보다도 사전 프로덕션 강화에 중점을 둔다는 점입니다. 그런 다음 남은 관심은 생산 시스템에 집중하는 데 투입됩니다. 프로덕션에서 안정적인 서비스를 구축하려면 이러한 순서를 바꿔야 합니다.<br />
최신 시스템에서는 엔지니어링에 대한 관심과 툴링의 대부분을 무엇보다도 프로덕션 시스템에 집중해야 합니다. 남은 관심의 주기는 스테이징 및 사전 프로덕션 시스템에 적용되어야 합니다. 스테이징 시스템에도 가치가 있습니다. 하지만 이는 본질적으로 부차적인 것입니다.<br />
스테이징 시스템은 프로덕션이 아닙니다. 프로덕션에서 일어나는 일을 결코 복제할 수 없습니다. 사전 프로덕션 시스템의 무균 실험실 환경은 실제 유료 서비스 사용자가 실제 환경에서 코드를 테스트하는 것과 동일한 조건을 모방할 수 없습니다. 그런데도 많은 팀이 여전히 프로덕션을 유리성으로 취급합니다.</p>
</blockquote>
<p>이건 꽤 급진적인 생각으로 느껴졌는데 생각해 보면 의미 있는 부분인 것 같다. 결국 가장 안전하게 하려면 사전 프로덕션 검증을 강화해야 하고 많은 기법이 여기에 초점이 맞춰져 있지만 시스템이 복잡해질수록 제대로 된 검증하기는 점점 어려워진다. 카오스 엔지니어링이 프로덕션에서 카오스를 만들었고 당시에도 급진적이라고 생각하면서도 멋지다고 생각했는데 아직 경험치가 부족하니 여전히 프로덕션은 좀 무섭게 느껴지는 것 같다. 이런 부분에서 생각의 전환을 할 필요가 있어 보인다.</p>
<blockquote>
<p>우리 팀은 회고전을 실행하여 문제를 분석하고, 미래의 우리 자신에게 문제를 처리하는 방법을 알려주는 런북을 작성하고, 다음번에 그 문제를 즉시 드러낼 수 있는 사용자 지정 대시보드(한두 개)를 만든 다음, 문제가 해결된 것으로 간주하고 넘어가곤 했습니다.</p>
</blockquote>
<p>이 부분은 자주 하는 행동이라 뜨끔하면서 반성하게 되었다.</p>
<blockquote>
<p>저는 소프트웨어 업계에서 영웅 문화의 이러한 측면을 강조하고 싶습니다. 램프 스택 스타일의 모놀리식 시스템에서 최후의 디버거는 일반적으로 시스템을 처음부터 구축한, 가장 오래 근무한 사람이 맡습니다. 가장 연차가 높은 엔지니어가 최후의 에스컬레이션 지점입니다. 이들은 가장 많은 상처 조직과 가장 많은 중단 목록을 가지고 있으며, 필연적으로 문제를 해결하기 위해 뛰어들어야 하는 사람들입니다.<br />
그 결과 이 영웅들은 진정한 휴가를 결코 가질 수 없습니다. 하와이로 신혼여행을 가던 중 새벽 3시에 호출을 받았는데, 몽고DB가 어떻게든 Parse API 서비스를 중단시켰기 때문이었습니다. 제 상사였던 CTO는 정말 미안해했습니다. 하지만 사이트는 다운되어 있었습니다. 한 시간 넘게 다운된 상태였는데 아무도 그 이유를 알아내지 못했습니다. 그래서 저를 호출했습니다. 네, 저는 불평했습니다. 하지만 마음 깊은 곳에서는 은근히 기분이 좋았습니다. 제가 필요했거든요. 제가 필요했으니까요.</p>
</blockquote>
<blockquote>
<p>2013년 Facebook이 Parse를 인수한 후, 저는 Facebook이 대부분의 실시간 분석에 사용하는 데이터 관리 시스템인 Scuba를 알게 되었습니다. 이 빠르고 확장 가능한 분산형 인메모리 데이터베이스는 초당 수백만 개의 행(이벤트)을 수집합니다. 실시간 데이터를 메모리에 완전히 저장하고 쿼리를 처리할 때 수백 대의 서버에 걸쳐 집계합니다. 제 경험은 거칠었습니다. 저는 Scuba의 사용자 환경이 매우 추악하고 심지어 적대적이라고 생각했습니다. 하지만 시스템 문제 해결에 대한 저의 접근 방식을 완전히 바꿔놓은 한 가지 뛰어난 기능이 있었으니, 바로 무한히 높은 카디널리티의 차원에 대해 거의 실시간으로 데이터를 슬라이스하고 주사위를 던질 수 있게 해준다는 것이었습니다.<br />
<br></p>
</blockquote>
<h2>Part 2. Fundamentals of Observability</h2>
<blockquote>
<p>메트릭이란 시스템 상태를 나타내기 위해 수집된 스칼라값을 의미하며, 이러한 숫자를 그룹화하고 검색하기 위해 선택적으로 태그가 추가될 수 있습니다. 메트릭은 소프트웨어 시스템에 대한 전통적인 모니터링의 기반이 되어 왔습니다</p>
</blockquote>
<blockquote>
<p>메트릭의 근본적인 한계는 사전 집계된 측정값(pre-aggregated measure)이라는 점입니다. 메트릭에 의해 생성된 수치는 미리 정의된 기간의 시스템 상태에 대한 집계된 보고서를 반영합니다.</p>
</blockquote>
<blockquote>
<p>메트릭은 모두 서로 연결되지 않은 별개의 측정값으로, 동일한 요청에 속하는 메트릭을 정확히 재구성하는 데 필요한 연결 조직과 세분성이 부족합니다.</p>
</blockquote>
<blockquote>
<p>메트릭은 미리 정의된 기간의 사전 정의된 관계를 종합적으로 수치로 표현한 것으로, 하나의 시스템 속성에 대한 좁은 관점의 하나에 불과합니다. 메트릭의 세부 수준이 너무 높고 시스템 상태를 다른 보기로 표시하는 기능이 너무 경직되어 있어 옵저버빌리티를 달성하기 어렵습니다. 메트릭은 옵저버빌리티의 기본 구성 요소로 사용하기에는 너무 제한적입니다.</p>
</blockquote>
<blockquote>
<p>옵저버빌리티 도구가 조사자에게 유용하려면 카디널리티가 높은 쿼리를 지원할 수 있어야 합니다. 최신 시스템에서는 새로운 문제를 디버깅하는 데 가장 유용한 많은 차원이 높은 카디널리티를 가지고 있습니다. 또한 깊이 숨겨진 문제를 찾기 위해 이러한 높은 카디널리티 차원을 함께 묶어(즉, 고차원성) 조사해야 하는 경우가 많습니다. 문제를 디버깅하는 것은 종종 건초 더미에서 바늘을 찾는 것과 같습니다. 높은 카디널리티와 고차원성은 매우 복잡한 분산 시스템 건초 더미에서 매우 세밀한 바늘을 찾을 수 있게 해주는 기능입니다.</p>
</blockquote>
<p>메트릭의 연결성이 필요하다는 것에는 동의한다. 물론 대부분 높은 카디널리티가 도움 된다는 것에도 동의할 것으로 생각한다. 높은 카니널리티를 담으면 메트릭 서버가 버티지 못해서 그렇지...</p>
<blockquote>
<p>분산 트레이싱(distributed tracing)은 애플리케이션을 구성하는 다양한 서비스에서 처리되는 단일 요청(trace라고 함)의 진행 상황을 추적하는 방법입니다. 이러한 의미에서 트레이싱은 "분산"되어 있으며, 그 기능을 수행하기 위해 단일 요청이 프로세스, 머신, 네트워크 경계를 넘나들어야 하는 경우가 많기 때문입니다.</p>
</blockquote>
<blockquote>
<p>모니터링 및 옵저버빌리티 커뮤니티는 공급업체 종속 문제를 해결하기 위해 수년 동안 여러 오픈 소스 프로젝트를 만들어 왔습니다. 2016년과 2017년에 각각 클라우드 네이티브 컴퓨팅 재단 산하의 OpenTracing과 Google이 후원하는 OpenCensus가 등장했습니다. 이 경쟁적인 개방형 표준은 가장 널리 사용되는 프로그래밍 언어용 라이브러리 세트를 제공하여 원격 분석 데이터를 수집하여 사용자가 선택한 백엔드로 실시간으로 전송할 수 있도록 했습니다. 결국 2019년, 두 그룹이 힘을 합쳐 CNCF 산하의 OpenTelemetry 프로젝트를 결성했습니다.</p>
</blockquote>
<p>분산 트레이싱은 알지는 꽤 되었지만 나도 그렇게 제대로 하기는 쉽지 않다. 물론 모든 기술이 그렇듯이 가까이서 기술적인 한계와 현실을 이해할수록 더 어렵게 느껴지긴 한다. OpenTelemetry은 최근에 조금씩 관심을 가지고 있는데 결국 OpenTelemetry로 가긴 하겠지만 긍정적인 미래와 걱정에 대한 생각이 둘다 있긴 하다.</p>
<blockquote>
<p>런북을 만드는 데 소요되는 시간이 대부분 낭비된다는 주장은 처음에는 다소 가혹해 보일 수 있습니다. 분명히 말씀드리자면, 특정 서비스의 요구 사항과 그 출발점을 팀에 빠르게 안내하기 위한 문서가 필요합니다.</p>
</blockquote>
<blockquote>
<p>그러나 가능한 모든 시스템 오류와 해결 방법을 포함하는 살아있는 문서를 유지하는 것은 쓸데없고 위험한 일입니다. 이러한 유형의 문서는 금방 부실해질 수 있으며, 잘못된 문서는 문서가 없는 것보다 더 위험할 수 있습니다. 빠르게 변화하는 시스템에서는 엔지니어의 의도(엔지니어가 이름을 지정하고 수집하기로 한 치수는 무엇인가?)와 프로덕션의 실시간 최신 상태 정보가 결합하여 있기 때문에 계측 자체가 최고의 문서가 되는 경우가 많습니다.</p>
</blockquote>
<p>문서에 대한 생각은 꽤 동의하는 편이다.</p>
<blockquote>
<p>옵저버빌리티의 진정한 힘은 문제를 디버깅하기 전에 너무 많은 것을 미리 알 필요가 없다는 것입니다. 시스템에 익숙하지 않은 경우에도 체계적이고 과학적으로 한 단계씩 단계를 밟아 단서를 따라 체계적으로 답을 찾을 수 있어야 합니다. 무언의 신호를 유추하거나, 과거의 상처 조직에 의존하거나, 익숙한 기지를 발휘하여 순식간에 올바른 결론에 도달하는 마법은 체계적이고 반복할 수 있으며 검증할 수 있는 프로세스로 대체됩니다.</p>
</blockquote>
<p>결국 이 책에서 얘기하는 것은 드릴다운 할 수 있어야 한다는 것이다. 예를 들어 특정 서비스에서 레이턴시가 갑자기 튀기 시작했을 때 메트릭은 그냥 레이턴시 평균이 튀었다는 것만 알려주지만 높은 카디널리티로 다양한 정보를 담아서 모든 메트릭 간에 연결할 수 있어야 한다는 것이다. 그래서 레이턴시가 튀었을 때 클릭해서 더 자세히 들어가서 이게 특정 서버에서 발생하는지, 특정 존에서만 발생하는지, 특정 API에서 발생하는지를 구분해서 볼 수 있어야 문제를 발견할 수 있다는 것인데 동의한다.</p>
<p>그리고 서버의 디스크나 하드웨어 등 시스템은 모니터링으로 파악하고 애플리케이션은 옵저버빌리티로 접근해야 한다는 부분도 수긍되었다.<br />
<br></p>
<h2>Part 3. Observability for Teams</h2>
<blockquote>
<p>이를 위한 가장 좋은 방법은 OpenTelemetry를 사용하여 애플리케이션을 계측하는 것입니다(7장 참조). OTel을 사용하면 다른 공급업체의 독점 에이전트나 라이브러리만큼 빠르고 쉽지는 않지만, 느리고 사용하기 어려운 것도 아닙니다. 처음부터 이 작업을 수행하는 데 필요한 약간의 사전 시간 투자는 나중에 여러 솔루션을 사용해 보고 어떤 솔루션이 가장 적합한지 확인하기로 할 때 큰 도움이 될 것입니다.</p>
</blockquote>
<p>이 책은 2022년 6월에 나왔고 지금과 마찬가지로 OpenTelemetry는 아직 초기 단계(내 개인 생각이다.)라고 할 수 있는데 요즘 분위기와 마찬가지로 OpenTelemetry의 미래를 아주 긍정적으로 보고 있고 분산 트레이스에서 OpenTelemetry이 해결책이 될 것으로 보고 계속 OpenTelemetry를 강조하고 있다.</p>
<blockquote>
<p>가장 큰 문제점에서 시작하는 것과 마찬가지로, 옵저버빌리티 도구를 직접 구축할지 아니면 상용 솔루션을 구입할지 결정하는 것은 투자 수익률(ROI)을 신속하게 입증하는 것입니다.</p>
</blockquote>
<blockquote>
<p>새로운 기술을 채택하는 데 있어 큰 장벽 중 하나는 매몰 비용 오류입니다. 개인과 조직은 이전에 투자한 시간, 비용, 노력의 결과로 행동이나 노력을 지속할 때 매몰 비용 오류를 범합니다.</p>
</blockquote>
<p>사서 쓰냐? 만들어서 쓰냐는 어려운 부분인데 꽤 여러 관점도 다뤄주어서 좋았다. 물론 모니터링은 간단하지 않으므로 조직이 작을 때는 그냥 사서 쓰고 조직이 커지면서 구축을 고민해야 한다고 생각하긴 한다.</p>
<blockquote>
<p>옵저버빌리티는 코드 로직을 디버깅하기 위한 것이 아닙니다. 옵저버빌리티는 시스템에서 디버깅에 필요한 코드를 찾을 수 있는 위치를 파악하기 위한 것입니다. 옵저버빌리티 도구는 문제가 발생할 수 있는 위치를 신속하게 좁혀서 도움을 줍니다.</p>
</blockquote>
<blockquote>
<p>관찰 가능성 중심 개발을 통해 엔지니어링 팀은 유리 성을 인터랙티브한 놀이터로 바꿀 수 있습니다. 프로덕션 환경은 고정된 것이 아니라 변화무쌍하며, 엔지니어는 어떤 게임에도 자신 있게 뛰어들어 승리를 거둘 수 있는 역량을 갖춰야 합니다. 그러나 옵저버빌리티를 SRE, 인프라 엔지니어 또는 운영팀의 영역으로만 간주하지 않을 때만 가능합니다. 소프트웨어 엔지니어는 옵저버빌리티를 채택하고 개발 관행에 적용하여 프로덕션에 변경을 가하는 것에 대한 두려움의 사이클을 풀어야 합니다.</p>
</blockquote>
<blockquote>
<p>소프트웨어 업계에서는 일반적으로 속도와 품질 간에 상충 관계가 발생한다는 인식이 있습니다. 즉, 소프트웨어를 빠르게 릴리스하거나 고품질 소프트웨어를 릴리스할 수는 있지만 둘 다 릴리스할 수는 없다는 것입니다. "Accelerate: Building and Scaling High Performing Technology Organizations”의 핵심 내용은 이러한 역관계는 잘못된 상식이라는 것입니다. 우수한 성과를 내는 기업에서는 속도와 품질이 함께 상승하며 서로를 강화합니다. 속도가 빨라지면 장애가 더 작아지고 발생 빈도가 줄어들며, 장애가 발생하더라도 복구하기가 더 쉬워집니다. 반대로 다음과 같은 팀의 경우 느리게 움직이는 팀은 실패가 더 자주 발생하고 복구하는 데 훨씬 더 오래 걸리는 경향이 있습니다.</p>
</blockquote>
<p>옵저버빌리티가 해결해야 하는 부분과 옵저버빌리티를 통해서 개발과 운영의 관행도 바꿔줄 수 있다는 부분은 꽤 좋았고 앞으로 옵저버빌리티의 미래에 대해서도 많은 인사이트를 얻을 수 있었다.</p>
<blockquote>
<p>CPU 사용률의 변화는 백업 프로세스가 실행 중이거나 가비지 컬렉터가 정리 작업을 수행하거나 시스템에서 다른 현상이 발생할 수도 있는 지표일 수 있습니다. 즉, 이러한 상태들은 우리가 실제로 관심을 가지는 문제뿐만 아니라 시스템의 다양한 요소들을 반영할 수 있습니다. 이러한 측정치를 기반으로 경고를 발생시키면 하드웨어 기반의 단순 측정치로 인해 오류가 발생할 확률이 높아집니다. 이러한 경험이 있는 엔지니어링 팀들은 이러한 유형의 경고를 무시하거나 억제하는 경향이 있으며, "그 경고 걱정하지 마세요; 우리는 이 프로세스가 가끔 메모리가 부족해질 때가 있다는 걸 알고 있습니다."와 같은 문구를 자주 사용합니다.</p>
</blockquote>
<blockquote>
<p>오류가 발생할 가능성이 높은 경고에 익숙해지는 것은 알려진 문제이자 위험한 관행입니다. 다른 산업에서는 이러한 문제를 "비정상적 허용(Normalization of Deviance)"이라고 합니다.</p>
</blockquote>
<blockquote>
<p>신뢰성 있는 서비스를 제공하려면 팀은 신뢰성이 떨어지거나 노이즈가 발생하는 경고를 제거해야 합니다. 그러나 많은 팀은 이러한 불필요한 방해를 제거하는 데 두려움을 느낍니다. 이러한 경고를 제거함으로써 서비스 저하에 대해 배우는 방법이 없다는 우려가 종종 우세합니다. 그러나 이러한 전통적인 경고 유형은 알려진 미지수(known-unknowns)만 감지하는 데 도움이 됩니다.</p>
</blockquote>
<blockquote>
<p>우리는 경고 기준을 두 부분의 하위 집합으로 정의합니다.<br />
첫째, 경고는 서비스의 사용자 경험이 저하된 상태를 신뢰성 있게 반영해야 합니다.<br />
둘째, 경고는 해결할 수 있어야 합니다.<br />
경고에 대응하여 디버깅하고 조치하는 데 체계적인 방법(단순 반복적인 자동화가 아닌)이 있어야 하며, 대응자가 올바른 조치 방법을 추측하지 않아도 되어야 합니다. 이 두 가지 조건이 충족되지 않는다면 구성한 경고는 더 이상 의도한 목적을 달성하지 못하는 것입니다.</p>
</blockquote>
<blockquote>
<p>전통적인 메트릭 기반의 모니터링 접근 방식은 정적인 임곗값을 사용하여 최적의 시스템 상태를 정의하는 데 의존합니다. 그러나 현대 시스템의 성능은 인프라 수준에서도 서로 다른 워크로드 하에서 동적으로 변할 수 있습니다. 정적인 임곗값은 사용자 경험에 미치는 영향을 모니터링하는 데 적합하지 않습니다.</p>
</blockquote>
<p>동의하는 부분이다. 요즘 업무를 하면서도 생각하지만 경고는 상당히 조심스럽게 접근해야 한다고 보는 편이다.(그래서 지금 하고 있는 업무에서도 경고를 아직까지 만들지 못하고 있다.) 경고는 신뢰성 있어야 하고 액션 가능해야 한다. 간단해 보이지만 아주 쉽지 않고 때문에는 알림을 끄는 기능도 필요해 지는데 이 끄는 기능도 결국 많이 꺼지면 신뢰성의 문제가 생겨서 쉽지 않다. 현실 구현을 제외하고 방향성에는 아주 동의한다.</p>
<blockquote>
<p>SLOs는 경고의 범위를 사용자들이 서비스를 체험하는 데 영향을 미치는 증상만을 고려하도록 좁힙니다.</p>
</blockquote>
<blockquote>
<p>"무엇(what)"과 "왜(why)"를 분리하는 것은 최대한 신호를 극대화하고 노이즈를 최소화하는 좋은 모니터링 작성에서 가장 중요한 차이 중 하나입니다.</p>
</blockquote>
<p>SLO를 설명하면서 얘기한 부분이 흥미로웠고 스터디에서도 여기서 많은 논의를 했는데 내가 이해한 핵심은 What과 Why를 구분하라는 것이다. 경고를 SLO로만 해야 하는데 이는 What을 알려주라는 것이고 Why는 옵저버빌리티 시스템에 들어와서 파악할 수 있게 제공해야 한다는 것이다.</p>
<blockquote>
<p>Slack CI의 주요 과제는 복잡성이었습니다. E2E 테스트의 실패는 코드 베이스, 인프라 및 플랫폼 런타임 변경사항이 복잡하게 상호작용한 결과일 수 있습니다. 2020년에 웹 앱 개발자의 단일 커밋에 대하여, 저희 CI 파이프라인은 30개 이상의 테스트 스위트를 GitHub를 통해 실행합니다. 이 파이프라인은 3개의 플랫폼 팀(퍼포먼스, 백엔드, 프론트엔드)과 20개의 다양한 요구사항과 전문 분야가 있는 팀/서비스에 의해 만들어졌습니다. 2020년 중반에 이르러 CI 인프라가 한계에 도달하기 시작했습니다. 테스트 실행이 월별로 10%씩 증가하면서 테스트 실행을 위해 여러 다운스트림 서비스를 확장하는 데 어려움을 겪었습니다.</p>
</blockquote>
<p>Slack의 게스트 챕터가 몇 편 나오는데 CI에서 옵저버빌리티를 사용해서 단계별 성능, 오류 문제를 옵저버빌리티를 사용해서 가시성을 높인 부분이 흥미로웠다. 그동안 옵저버빌리티를 서비스 위주로만 생각해서 이런 식의 적용은 미처 생각해 보지 못했다.<br />
<br></p>
<h2>Part 4. Observability at Scale</h2>
<blockquote>
<p>사람들은 일반적으로 자신의 시간 가격을 고려하는 데 익숙하지 않습니다. 인프라를 가동하고 소프트웨어를 구성하는 데 한 시간이 걸리면 DIY 솔루션은 본질적으로 무료인 것처럼 느껴집니다.</p>
</blockquote>
<p>오픈소스를 가져가다 구축할 때 흔히들 하는 실수.</p>
<blockquote>
<p>옵저버빌리티는 조직의 경쟁 우위입니다. 자체 옵저버빌리티 솔루션을 구축하면 자체 관행과 문화에 깊이 뿌리내리고 기존 기관의 지식을 활용하는 솔루션을 개발할 수 있습니다. 많은 워크플로우 및 구현과 함께 작동하도록 설계된 일반적인 사전 구축 소프트웨어를 사용하는 대신, 자체 규칙에 따라 비즈니스의 맞춤형 부분과 긴밀하게 통합되도록 솔루션을 사용자 지정할 수 있습니다.</p>
</blockquote>
<blockquote>
<p>자체 옵저버빌리티 솔루션을 구축하기로 할 때는 조직의 능력과 상용 시스템보다 더 나은 것을 개발할 수 있는 가능성을 모두 현실적으로 고려하는 것이 중요합니다. 조직 전체에서 채택을 장려하는 데 필요한 사용자 인터페이스, 워크플로우 유연성 및 속도를 갖춘 시스템을 제공할 수 있는 조직적 전문성을 갖추고 있습니까? 그렇지 않다면 솔루션의 단점과 해결 방법을 잘 알고 있는 사람들 외에는 널리 채택되지 않는 솔루션에 시간과 비용을 투자하고 비즈니스 기회를 잃게 될 가능성이 높습니다.</p>
</blockquote>
<blockquote>
<p>상용 솔루션의 또 다른 숨겨진 비용은 시간입니다. 예, 기성 솔루션을 구매하면 가치 실현 시간을 단축할 수 있습니다. 하지만 이 경로를 선택할 때는 벤더 종속이라는 숨겨진 함정에 유의해야 합니다.</p>
</blockquote>
<blockquote>
<p>옵저버빌리티 도구에 있어 구축 또는 구매라는 선택은 잘못된 이분법입니다. 선택은 단순히 구축 또는 구매로만 제한되지 않습니다. 세 번째 옵션은 구매 후 구축하는 것입니다. 실제로 이 책의 저자는 대부분의 조직에 이 접근 방식을 권장합니다. 내부 기회비용을 최소화하고 조직의 고유한 요구 사항에 맞는 솔루션을 구축할 수 있습니다.</p>
</blockquote>
<p>나에게 16장 Efficient Data Storage는 가장 재밌는 부분 중 하나였다. 데이터 스토리지에 대해 많이 생각한 적이 없긴 한데 결국 책에서 얘기하는 높은 카디널리티를 고차원으로 모든 메트릭을 저장하려면 스토리지가 문제가 된다. 앞부분 읽으면서는 이런 마법의 스토리지가 있다고? 하는 느낌으로 읽을 때도 있지만 여기서 현실적으로 어떤 스토리지가 있고 각 스토리지의 장단점, 옵저버빌리티를 하려면 어떤 부분은 잘 되지만 어떤 부분에서 한계가 있는지를 잘 설명해 주고 있다. 결국 하이브리드 형을 제안하긴 하는데 현재의 기술적 상황을 잘 지적해 주었다는 느낌이 들었다.<br />
<br></p>
<h2>Part 5. Spreading Observability Culture</h2>
<blockquote>
<p>옵저버빌리티의 목표는 엔지니어링 팀이 시스템을 개발, 운영, 철저하게 디버그하고 보고할 수 있는 역량을 제공하는 것입니다. 팀은 시스템 동작을 더 잘 이해하기 위해 시스템에 대해 임의의 질문을 함으로써 호기심을 탐구할 수 있는 권한을 부여받아야 합니다. 팀원들이 도구와 경영진의 지원을 통해 능동적으로 시스템을 조사할 수 있도록 인센티브를 제공해야 합니다.</p>
</blockquote>
<blockquote>
<p>데브옵스 관행이 계속해서 주류로 자리 잡으면서, 미래 지향적인 엔지니어링 리더십 팀은 엔지니어링 팀과 운영팀 사이의 장벽을 제거합니다. 이러한 인위적인 장벽을 제거하면 팀이 소프트웨어 개발과 운영에 대해 더 많은 주인의식을 가질 수 있습니다. 옵저버빌리티는 대기 경험이 부족한 엔지니어가 장애가 발생하는 위치와 장애를 완화하는 방법을 더 잘 이해할 수 있도록 지원하여 소프트웨어 개발과 운영 사이의 인위적인 벽을 허물어뜨립니다.</p>
</blockquote>
<blockquote>
<p>팀이 옵저버빌리티의 이점을 누리게 되면, 프로덕션의 이해와 운영에 대한 신뢰 수준이 높아질 것입니다. 해결되지 않은 '미스터리' 인시던트의 비율이 줄어들고 조직 전체에서 인시던트를 감지하고 해결하는 데 걸리는 시간이 단축될 것입니다. 그러나 이 시점에서 성공을 측정할 때 자주 저지르는 실수는 탐지된 전체 인시던트 수와 같은 얕은 메트릭에 지나치게 집착하는 것입니다.</p>
</blockquote>
<p>옵저버빌리티에 대한 전체적인 생각의 틀을 잡게 해준 좋은 책이라고 생각하고 저자들이 옵저버빌리티에 대해 정말 오래 고민했다는 것도 느낄 수 있었다. 그래서 이 책이 서비스 홍보 책은 아니지만, 또 전혀 아니라고 할 수도 없기에 Honeycomb.io를 한번 써보고 싶다는 생각이 들었다. 가볍게 적용해 볼 옵저버빌리티가 있다면 테스트 삼아 한번 써볼 것 같긴 하다. 물론 개인적으로는 책 초반에 배경 설명이 좀 길고 반복되는 느낌이라서 좀 더 짧은 분량으로도 저자들의 의도를 잘 전달할 수 있지 않았을까 하는 생각도 든다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1688?commentInput=true#entry1688WriteComment">댓글 쓰기</a></strong></p>Infcon 2023에서 발표한 "DevOps를 가속화하는 플랫폼 엔지니어링"Outsiderhttps://blog.outsider.ne.kr/16842024-01-11T01:32:19+09:002023-08-18T00:46:25+09:00<p>지난 8월 15일 <a href="https://inflearn.com/conf/infcon-2023">Infcon 2023</a>에서 <a href="https://inflearn.com/conf/infcon-2023/session-detail?id=782">"DevOps를 가속화하는 플랫폼 엔지니어링"이라는 제목으로 발표</a>를 했다.</p>
<p>Infcon은 IT 관련 온라인 강의를 제공하는 <a href="https://www.inflearn.com/">Inflearn</a>에서 작년부터 시작한 콘퍼런스다. 인프런에서 강의를 하고 있지는 않지만 인프런 초창기부터 인프런 대표님과 티타임을 몇 번 가졌던 인연으로 관심도 가지고 응원하는 회사이다. 물론 코로나로 인해 오랫동안 오프라인 콘퍼런스를 참석 못 하다가 작년 <a href="https://infcon.day/session/">Infcon 2022</a>에서 사람들을 보며 즐거웠던 기억이 있었는데 이번에는 발표자로 참석하게 되었다.</p>
<p>처음 발표할지 말지 고민하기 시작했을 때 아무래도 요즘 하는 업무가 사내에서 배포 시스템을 만드는 것이라서 이 부분과 관련해서 고민했고 다른 주제가 생각나는 것도 없기는 했다.</p>
<p>지난 2년 동안 배포와 DevOps에 관한 고민을 많이 했고 여기서 플랫폼적인 접근하면서 <a href="https://platformengineering.org/">Platform Engineering</a>도 알게 되고 인프라 업무와 관련한 내 생각은 꽤 많이 달라졌다. 그래서 작년에 <a href="https://blog.outsider.ne.kr/1635">Google Cloud Next Innovators Hive: Korea</a>에서도 인프라 업무에 플랫폼 접근에 관한 얘기를 한번 설명하기도 했고 <a href="https://www.youtube.com/watch?v=JWR8pEPimsY&list=PLaHcMRg2hoBopbyEOW1XjP3runE93n9GC">지난 6월에는 회사에서 세미나도 했기 때문에 이때는 좀 더 편하게 실제 업무로 어떤 걸 만들고 있는지 공유</a>하기도 했다.</p>
<p>주제는 DevOps, 플랫폼 엔지니어링 등으로 생각하고 있었지만, 전달할 자신이 없었다. 기술적인 내용이면서도 기술적인 내용이 아니기도 하고 업무를 하면서 구체적인 부분에서는 어느 정도 명확하게 생각을 정리할 수 있지만 이걸 스토리로 풀어서 다른 사람에게 이해시키는 것은 어렵게 느껴졌다.</p>
<p>보통 발표할 때 내가 말하고 싶은 명확한 주제, 전달하고 싶은 메시지가 명확해야 발표 자료를 잘 만들 수 있는데 이 메시지가 명확하지 않았다. 파편적으로 얘기할 내용은 있었지만, 전체를 관통하는 주제가 좀 애매했기에 아무래도 발표하기가 어렵겠다고 생각했다.</p>
<p>이 발표와는 별개로 팀에서 플랫폼 엔지니어링 관련 업무를 하면서 (나도 아직 공부 중이지만) 내가 생각하는 플랫폼 엔지니어링이 무엇이고 지금 우리가 만드는 제품을 어떻게 접근해야 한다고 생각하는지를 공유할 자리가 있었다. 개념적인 거라 잘 전달이 될지 공감을 얻을 수 있을지는 몰랐지만, 그동안 업무를 하면서 생각에 차이가 발생한다고 느낀 부분을 한번 정리해서 공유해서 서로에 대한 이해도를 높일 필요가 있다고 생각했다. 이 공유도 몇 달 전부터 하겠다고 얘기했지만 잘 정리되지 않아서 미루다가 결국 했는데 이때 자료를 준비하면서(아마도 팀 내니까 구체적인 얘기도 더 편히 할 수 있어서) 생각이 많이 정리되었다.</p>
<p>그 덕에 공개적인 발표에서도 정리할 수 있을 거라는 생각이 들었고 발표하기로 하고 대략적인 스토리를 고민한 뒤 발표하게 되었다.</p>
<script defer class="speakerdeck-embed" data-id="99cff960de9944fca93e8922c36de783" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>
<p>플랫폼 엔지니어링이 막 퍼질지 어떨지 모르지만 난 꽤 좋은 접근이라고 생각하고 있기 때문에 그러한 이유를 설명하고 싶었고 내 고민의 결론은 결국 그 중심에 DevOps가 있고 이를 플랫폼으로 대응함으로써 서비스 쪽 소프트웨어 엔지니어와 인프라팀의 문제를 다 해결할 수 있다고 생각하고 있다.</p>
<p>머릿속에서는 명확하게 느껴지는데 이를 자연스럽게 이어지는 스토리로 만들어서 발표 자료를 만드는 건 꽤 어려웠다. 발표 날짜가 꽤 다가왔을 때도 발표 자료를 정리하면서 "난 도대체 무슨 이야기를 하고 싶은 건가?", "듣는 사람에게 과연 무슨 말을 하려는지 전달이 될까?", "너무 뻔한 말을 주저리주저리 한다고 생각하진 않을까?" 같은 생각이 들면서 계속 정리를 했다. 그래도 계속 다듬다 보니 어느 정도는 정리가 된 거 같다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4491825009.jpg" width="720" height="540" alt="발표 후 QnA 모임" title="" /></p>
<p>발표가 끝나고 QnA 시간이 따로 있었다. 발표장에서 바로 질문을 받는 것이 아니라 끝나면 QnA 깃발을 든 인프콘 스탭을 따라 2층에 마련된 공간으로 가면 질문하고 싶은 사람들이 같이 이동하는 구조였다. 보통 궁금한 게 있어도 발표 끝나고 내려온 발표자를 붙잡고 물어보다가 다음 발표가 있기 때문에 밀려 나오면서 제대로 얘기 못 하거나 물어볼까 말까 하다가 못 묻는 경우도 많은데 이렇게 자리를 마련해준 건 꽤 좋았다.</p>
<p>물론 누가 질문을 하러 오려나? 싶긴 했는데 꽤 많은 분이 와서 다양한 질문을 해주셨다.(사진의 뒤에 많은 사람이 모여있는 곳은 같은 시간대에 발표함 Toby 님의 QnA 그룹이다) 발표보다는 편한 자리였기에 내 생각을 더 풀어서 설명할 수 있어서 나도 꽤 재미가 있었고 발표에서 미처 다 설명하지 못한 내용도 얘기할 수 있어서 좋았다.</p>
<p>발표가 마지막에서 두 번째 타임이라서 편한 마음으로 사람들 만나고 다니진 못했지만 그래도 오랜만에 개발자를 만나서 이야기할 수 있어서 즐거웠다.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/cBf3iBFktlE?si=j1q0q23S1hhglhBk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong><a href="https://blog.outsider.ne.kr/1684?commentInput=true#entry1684WriteComment">댓글 쓰기</a></strong></p>내부 개발자 플랫폼(IDP) 구축을 도와주는 HumanitecOutsiderhttps://blog.outsider.ne.kr/16802023-07-22T16:56:27+09:002023-07-22T16:56:27+09:00<p>Humanitec은 유명한 서비스는 아니다. 나도 <a href="https://platformengineering.org/blog/what-is-platform-engineering">Platform Engineering</a>을 공부하다가 알게 된 회사고 <code>platformengineering.org</code>도 Humanitec이 운영하고 있다.</p>
<p>플랫폼 엔지니어링이란 용어를 가장 적극적으로 퍼트리고 있는 회사라고 할 수 있다. 플랫폼 엔지니어링이란 용어가 여전히 모호한 부분이 있지만 나에게 가장 중요한 부분은 해당 조직의 문제에 맞춰서 최적화하는 부분이라고 생각하기에 플랫폼 엔지니어링으로 사업을 하는 Humanitec이 계속 궁금했다. 어떻게 사업을 하는지도 궁금했지만, 플랫폼 엔지니어링을 아무래도 가장 많이 오랫동안 고민했을 거로 생각했기에 어떤 제품을 만들었을지 궁금해하던 차에 현재 하는 스터디 모임에서 발표를 해야할 일이 있어서 겸사겸사 Humanitec을 살펴봤다.<br />
<br></p>
<h1>Humanitec</h1>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/7095933809.jpg" width="750" height="378" alt="Humanitec 홈페이지" title="" /></p>
<p><a href="https://humanitec.com/">Humanitec 사이트</a>를 보면 Humanitec으로 내부 개발자 플랫폼(IDP, Internal Developer Platform)을 적은 비용으로 빠르게 만들어서 다음을 이룰 수 있다고 설명하고 있다.</p>
<ul>
<li>표준화를 통해서 운영 업무를 줄이고</li>
<li>셀프 서비스를 통해 리드 타임을 줄이고</li>
<li>안전한 릴리스로 배포 주기를 줄일 수 있다.</li>
</ul>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/7752125068.jpg" width="750" height="236" alt="Score + Platform Orchestrator + Drivers" title="" /></p>
<p>Humanitec은 <a href="https://humanitec.com/products/score">Score</a>, <a href="https://humanitec.com/products/platform-orchestrator">Platform Orchestrator</a>, <a href="https://humanitec.com/open-source-drivers">Drivers</a> 3가지로 구성되어 있다.</p>
<p><a href="https://humanitec.com/products/score">Score</a>는 오픈소스로 공개되어 있는데 이 서비스가 어떤 구성으로 배포되어야 하는지를 정의하는 워크로드 명세서이다. 예를 들어 어떤 컨테이너 이미지를 사용하고 포트는 무엇이고 데이터베이스나 캐시는 무엇을 쓰는지를 정의해 놓고 배포할 때 어떻게 구성해야 하는지 알 수 있게 하는 것이다.</p>
<p><a href="https://humanitec.com/products/platform-orchestrator">Platform Orchestrator</a>는 문서에는 룰 엔진이라고 되어 있는데 그 정체가 아주 명확하지는 않지만 Humanitec 서비스 그 자체라고 이해해도 된다. 결국 이 인프라스트럭처를 표준화한다는 것은 인프라를 미리 만들어 두고 배포 시에 워크로드와 인프라를 연결하게 되는데 Score로 정의한 워크로드 명세를 가지고 어떤 인프라와 연결해야 할지 정책을 위반하진 않았는지를 검사한다.</p>
<p><a href="https://humanitec.com/open-source-drivers">Drivers</a>는 인프라스트럭처를 프로비저닝하는 방법을 정의하는 방법을 제공한다. 데이터베이스나 캐시를 미리 만들어 둘 수도 있지만 어떻게 만들 수 있는지를 정의할 수 있다면 배포가 나가면서 해당 인프라스트럭처를 만들면서 워크로드와 연결해서 배포할 수 있는데 이를 Drivers로 할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/3127905338.jpg" width="750" height="293" alt="Score + Platform Orchestrator + Drivers가 연결된 배포 파이프라인" title="" /></p>
<p>세 가지 제품이 실제 개발 프로세스에서 어떻게 연결되는지를 좀 더 설명한 그림이다. Score 파일은 워크로드의 명세이므로 프로젝트의 저장소에 위치한다. CI에서 컨테이너에 이미지를 푸시하고 Platform Orchestrator에 요청하면 Platform Orchestrator는 Terraform 등으로 미리 만들어 놓은 리소스와 연결하거나 Drivers를 이용해서 그 순간 새로 리소스를 생성하고 워크로드와 연결해서 배포를 시작한다.</p>
<p>내가 업무로 만들고 있는 것도 비슷한 형태이기도 하고 이런 플랫폼을 구축하려면 필요한 것들이 많은데 각 컴포넌트를 제공하고 이를 이용해서 회사 내부에서 조직에 맞는 내부 개발자 플랫폼(IDP)을 빠르게 구축하라는 게 Humanitec의 컨셉이라고 이해했다.<br />
<br></p>
<h2>Humanitec은 유용한가?</h2>
<p>위에서 정리해서 설명했지만 여기까지 이해하기가 꽤 어려웠다. Humanitec의 글을 종종 보던 터라 기대하고 있었는데 만든 제품에 실망을 꽤 했다.</p>
<p>문서가 너무 부실한 상태이다. 여기서 말하는 Platform Engineering이 담아야 하는 영역이 꽤 크기 때문에 욕심이 컸던 것인지 문서가 너무 흩어져 있고 전체 컨셉을 이해하기 어렵다. 튜토리얼처럼 따라 해 보면 간단한 앱을 실제로 배포해 주면서 Humanitec이 제공하고자 하는 핵심을 빠르게 경험해 주는 게 제일 좋고 이를 위한 예제 구성도 잘 되어 있는 거 같은데 가능한 영역을 다 설명하고자 하는 욕심 때문인지 이렇게 따라가 볼 수 있는 문서 경로가 없어서 컨셉을 이용하는 데 상당한 시간이 걸렸다.</p>
<p>실제 프로덕트를 만들기 시작한지는 얼마 안되어서 인지 부실한 부분이 많다. Platform Engineering의 영역 때문인지도 모르지만 한번에 너무 큰 욕심을 보여서 전체가 부실한 느낌이 많이 들었다. 제품의 컨셉을 이해한 뒤에도 실제로 잘 동작할 거라는 의심이 꽤 들었고 AWS, GCP, Azure의 리소스를 다 커버하면서 Kubernetes 등까지 커버하려다 보니까 너무 규모가 큰데 아직 그 모든 규모를 커버할 역량은 안되어 보이기 때문에 실제로 완성도 있게 갈 수 있을지가 의심되는 상황이었다. 결국 드라이버만 봐도 Terrafrom처럼 모든 프로바이더에 대한 리소스 구현이 다 되어야 하는데 회사에 엄청난 노력이 있거나 오픈소스 생태계를 구성해야 하는데 현재로서는 둘 다 힘들어 보인다. 물론 Terraform 등 다른 도구로 리소스를 프로비저닝하고 이를 Platform Orchestrator에 등록해서 사용하는 것도 가능하지만 이조차도 오히려 복잡도를 너무 늘리지 않나 하는 생각이 들었다.</p>
<p>어느 정도 파악한 뒤의 느낌은 뭘 하고자 하는지는 이해했고 어느 정도 동의도 하지만 가능한가에 대한 의구심이 든 상태이다.<br />
<br></p>
<h1>humanitec으로 앱 배포해 보기</h1>
<p>실제로 가장 간단한 구성을 통해 Score, Platform Orchestrator, Drivers를 사용해서 앱을 배포해 보는 것이 Humanitec의 제품 의도를 이해해 보기 제일 좋다고 생각한다. 어떻게 사용하는지를 보면 대충 그 의도를 이해할 것이다.</p>
<p>먼저 <a href="https://docs.score.dev/docs/get-started/install/">Score CLI를 설치</a>해야 한다. 이것도 이유는 모르겠지만 <code>score-compose</code>, <code>score-helm</code>, <code>score-humanitec</code> 3개의 CLI가 있어서 더욱 헷갈리는데 나는 가장 범용적으로 보이는 <code>score-humanitec</code>를 사용했다.</p>
<p>아래 내용으로 <code>score.yaml</code> 파일을 만든다.</p>
<pre class="line-numbers"><code class="language-yaml">apiVersion: score.dev/v1b1
metadata:
name: hello-world
service:
ports:
www:
port: 80
containers:
hello:
image: nginx
resources:
dns:
type: dns
</code></pre>
<p>컨테이너와 서비스 그리고 연결할 DNS 리소스가 정의된 것을 볼 수 있다. 가장 간단하게 구성하느라 코드도 사용하지 않고 <code>nginx</code> 컨테이너를 그대로 사용했다.</p>
<p>앱을 배포하기 전에 <a href="https://humanitec.com/">Humanitec</a>에 가입해서 조직을 만들면 그 아래에서 앱을 만들 수 있다. 여기서는 <code>demo</code>라는 앱을 만들었다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/6743933481.jpg" width="750" height="248" alt="Humanitec의 어플리케이션 탭" title="" /></p>
<p>demo 앱 상세로 들어가 보면 Development 환경이 기본으로 생성되어 있다. 실제 배포한다면 staging, production 등 추가 환경을 만들어서 쓰겠지만 여기서는 Development만 그대로 사용했고 아직 배포하지 않았으니 아무 기록도 없다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/5094661468.jpg" width="750" height="161" alt="Humanitec에 생성한 앱" title="" /></p>
<p>아까 설치한 <code>score-humanitec</code>를 이용해서 배포하자. <code>delta</code>는 Score 파일에서 Humanitec의 배포 델타를 생성하는 명령어이고 <code>--deploy</code> 옵션을 사용하면 바로 배포를 진행한다. 여기선 로컬에서 바로 하지만 실제 사용한다면 CI/CD 파이프라인에 CLI를 설치해서 요청을 보낼 것이다.</p>
<pre class="line-numbers"><code class="language-bash">$ score-humanitec delta \
--env development \
--app demo \
--org="${HUMANITEC_ORG}" \
--token "${HUMANITEC_TOKEN}" \
--deploy
{
"id": "b98217ea705bcfac33ab0e94c19a0538f006d64d",
"metadata": {
"env_id": "development",
"name": "Auto-generated (SCORE)",
"url": "https://app.humanitec.io/orgs/your_org/apps/demo/envs/development/draft/b98217ea705bcfac33ab0e94c19a0538f006d64d",
"created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
"created_at": "2023-07-22T05:59:34.570576078Z",
"last_modified_at": "2023-07-22T05:59:34.570576078Z"
},
"modules": {
"add": {
"hello-world": {
"externals": {
"dns": {
"type": "dns"
}
},
"profile": "humanitec/default-module",
"spec": {
"containers": {
"hello": {
"id": "hello",
"image": "nginx"
}
},
"service": {
"ports": {
"www": {
"container_port": 80,
"protocol": "TCP",
"service_port": 80
}
}
}
}
}
}
}
}
</code></pre>
<p>다시 Humanitec 사이트에 가보면 배포가 된 것을 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/9733120026.jpg" width="750" height="327" alt="Humanitec에 표시된 배포 상태" title="" /></p>
<p>배포된 앱의 상세에 가면 컨테이너의 로그 등을 볼 수 있고 연결된 DNS, 인그레스, 서비스 어카운트 등의 상태를 볼 수 있고 replicas 수의 조정 등 운영을 셀프서비스로 직접 할 수 있게 되어 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/7716544115.jpg" width="750" height="506" alt="배포된 버전의 상세 정보" title="" /></p>
<p><code>score.yaml</code> 파일에서 DNS 리소스를 연결했으므로 Humanitec에서 임시 도메인도 연결해 준다. DNS는 설정되었지만, 아직 실제 컨테이너로 연결은 되지 않는다.</p>
<p>여기서 따로 명시가 없었음에도 Kuberentes로 배포된 거를 볼 수 있다. 이는 테스트를 쉽게 해볼 수 있도록 Humanitec에서 미리 리소스를 만들어 두었기 때문이다. 리소스 탭을 보면 Kubernetes 클러스터뿐 아니라 DNS, 인그레스, 데이터베이스 등 테스트용 리소스가 정의된 걸 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4631519856.jpg" width="750" height="388" alt="Humanitec의 리소스 관리 탭" title="" /></p>
<p>Default Humanitec Cluster에 들어가 보면 방금 배포한 demo 앱이 연결된 것을 확인할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/1440988257.jpg" width="750" height="223" alt="Kubernetes 클러스터 리소스에 demo 앱이 연결됨" title="" /></p>
<p>앞에서 배포할 때 로그를 보면 <code>humanitec/default-module</code>라는 프로필을 사용한 것을 알 수 있는데 이 프로필에 기본 클러스터 등이 연결되어 있을 것으로 추측한다. 이 프로필 관리는 어디서 하는지는 잘 모르겠다.</p>
<p>여기서 알 수 있는 것은 조직 내 인프라팀에서 용도에 따라 프로필을 만들고 그 아래 다양한 리소스를 생성해서 등록해 놓으면 서비스 개발팀에서 배포할 때 자동으로 연결되어서 배포되므로 인프라팀은 리소스를 표준화해서 관리하고 서비스 개발팀은 셀프서비스로 인프라 정책을 어기지 않고 빠르게 배포할 수 있게 한다는 것이다.</p>
<p>다시 돌아와서 컨테이너에 트래픽을 연결하려면 인그레스를 설정해야 한다.</p>
<p><code>score.yaml</code> 파일 옆에 <code>humanitec.score.yaml</code>라는 파일을 만들고 다음 내용을 입력한다. 이는 Score에서는 <a href="https://docs.score.dev/docs/reference/humanitec-extension/">Humanitec extension reference</a>라고 부르는데 Humanitec에 특화된 부분을 확장하기 위한 명세라고 이해하면 된다. 아무래도 Score를 오픈소스로 만들어서 벤더 중립적으로 만들려다 보니 Humanitec에 특화된 부분을 별도로 분리한 것 같은데 이는 현시점에서는 좀 불필요한 복잡성이 아닌가 하는 생각이 들었다. 아래 설정에서는 인그레스를 추가했다.</p>
<pre class="line-numbers"><code class="language-yaml">apiVersion: humanitec.org/v1b1
profile: "humanitec/default-module"
spec:
"ingress":
rules:
"${resources.dns}":
http:
"/":
type: prefix
port: 80
</code></pre>
<p>아까처럼 배포하지만 <code>--extensions ./humanitec.score.yaml</code> 옵션을 주어서 다시 한번 배포한다.</p>
<pre class="line-numbers"><code class="language-bash">$ score-humanitec delta
--env development \
--app demo \
--org="${HUMANITEC_ORG}" \
--token "${HUMANITEC_TOKEN}" \
--extensions ./humanitec.score.yaml \
--deploy
{
"id": "99fc6280b5e3457221ac52055fc259232c49bcf2",
"metadata": {
"env_id": "development",
"name": "Auto-generated (SCORE)",
"url": "https://app.humanitec.io/orgs/your_org/apps/demo/envs/development/draft/99fc6280b5e3457221ac52055fc259232c49bcf2",
"created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
"created_at": "2023-07-22T06:19:03.070519216Z",
"last_modified_at": "2023-07-22T06:19:03.070519216Z"
},
"modules": {
"add": {
"hello-world": {
"externals": {
"dns": {
"type": "dns"
}
},
"profile": "humanitec/default-module",
"spec": {
"containers": {
"hello": {
"id": "hello",
"image": "nginx"
}
},
"ingress": {
"rules": {
"externals.dns": {
"http": {
"/": {
"port": 80,
"type": "prefix"
}
}
}
}
},
"service": {
"ports": {
"www": {
"container_port": 80,
"protocol": "TCP",
"service_port": 80
}
}
}
}
}
}
}
}
</code></pre>
<p>이제 배포에 다시 들어가 보면 인그레스가 설정된 것을 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/8138074426.jpg" width="750" height="621" alt="배포된 앱의 인스레스 정보" title="" /></p>
<p>해당 도메인에 연결하면 배포한 nginx 페이지가 잘 나오는 것을 확인할 수 있고 당연히 앞에서 본 리소스 탭의 인그레스에도 demo 앱에서 연결되어 있다고 표시된다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4190139070.jpg" width="500" height="215" alt="연결된 nginx 페이지" title="" /></p>
<p>배포하면서 리소스 연결하는 것을 살펴봤으니 이제 리소스 생성은 어떻게 하는지 Drivers를 살펴보자.</p>
<p><a href="https://humanitec.com/open-source-drivers">Drivers</a>는 오픈소스라고 하는데 저장소는 못 찾았다. <a href="https://developer.humanitec.com/drivers/getting-started/">문서</a>를 보아도 아직 많은 리소스의 Drivers는 없고 AWS에는 <a href="https://developer.humanitec.com/drivers/built-in-drivers/dns-aws-route53/">Route53</a>만 있어서 이를 사용해 보았다. 물론 커스텀 드라이버를 직접 만들 수도 있기는 하고 그냥 Terraform을 이용해서 리소스를 만든 뒤에 API로 Humanitec에 등록하는 것도 가능하다.</p>
<p>먼저 AWS를 사용해야 하니 Cloud Accounts에서 AWS의 API 키와 시크릿을 등록한다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/9736066697.jpg" width="750" height="205" alt="Humanitec의 클라우드 어카운트 탭" title="" /></p>
<p>그리고 <code>https://api.humanitec.io/orgs/your_org/resources/defs</code> API를 이용해서 <code>humanitec/dns-aws-route53</code> 드라이버를 등록한다.</p>
<pre class="line-numbers"><code class="language-bash">$ curl https://api.humanitec.io/orgs/your_org/resources/defs \
-X POST \
-H "Authorization: Bearer $HUMANITEC_TOKEN" \
-H "Content-Type: application/json" \
--data-binary '{
"id": "route53",
"name": "Route53",
"type": "dns",
"criteria": [
{
"app_id": "demo"
}
],
"account_id": "aws",
"driver_type": "humanitec/dns-aws-route53",
"driver_inputs": {
"values": {
"domain": "example.outsider.domain",
"hosted_zone_id": "Z01234567890J"
}
}
}'
{
"org_id": "your_org",
"id": "route53",
"name": "Route53",
"type": "dns",
"driver_type": "humanitec/dns-aws-route53",
"driver_inputs": {
"values": {
"domain": "example.outsider.domain",
"hosted_zone_id": "Z01234567890J"
}
},
"created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
"created_at": "2023-07-22T06:44:03.69865496Z",
"criteria": [{
"app_id": "demo",
"id": "8b5b5a186b2029e4"
}]
}
</code></pre>
<p>Humanitec의 Resource Management에 가보면 Route53이라는 리소스가 등록된 것을 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/4365944464.jpg" width="750" height="244" alt="리소스 관리에 등록된 AWS Route53 리소스" title="" /></p>
<p>이것만으로는 Route53을 생성하지 않는다. 아까 배포했던 <code>score.yaml</code> 파일을 다음과 같이 수정한다.</p>
<pre class="line-numbers"><code class="language-yaml">apiVersion: score.dev/v1b1
metadata:
name: hello-world
service:
ports:
www:
port: 80
containers:
hello:
image: nginx
resources:
route53:
type: dns
</code></pre>
<p>달라진 부분은 리소스의 dns 부분인데 아까는 아래처럼 되어 있어서 dns 타입의 이름이 <code>dns</code>였지만 이번에는 새로 생성한 리소스인 <code>route53</code>으로 변경했다.</p>
<pre class="line-numbers"><code class="language-yaml">resources:
dns:
type: dns
</code></pre>
<p>다시 배포를 진행하면 AWS Route53에 A 레코드가 바로 생성되는 것을 볼 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/6819159708.jpg" width="750" height="272" alt="AWS Route53에 등록된 A 레코드" title="" /></p>
<p>이렇게 Drivers를 이용하면 어떻게 리소스를 생성하는지를 정의해 두고 서비스 개발팀에서는 Score 파일에서 리소스를 정의하면 배포 시점에 생성되면서 연결해서 바로 사용할 수 있게 된다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1680?commentInput=true#entry1680WriteComment">댓글 쓰기</a></strong></p>