반복영역 건너뛰기
주메뉴 바로가기
본문 바로가기
제품/서비스
EMS Solution
Features
클라우드 관리
AI 인공지능
서버관리
데이터베이스 관리
네트워크 관리
트래픽 관리
설비 IoT 관리
무선 AP 관리
교환기 관리
운영자동화
실시간 관리
백업 관리
APM Solution
애플리케이션 관리
URL 관리
ITSM Solution
서비스데스크
IT 서비스 관리
Big Data Solution
SIEM
Dashboard
대시보드
Consulting Service
컨설팅 서비스
고객
레퍼런스
고객FAQ
문의하기
가격
자료실
카탈로그
사용자매뉴얼
회사소개
비전·미션
연혁
2016~현재
2000~2015
인증서·수상
투자정보
재무정보
전자공고
IR자료
새소식
공고
보도자료
오시는 길
채용
피플
컬처
공고
FAQ
블로그
열기
메인 페이지로 이동
블로그
최신이야기
블로그
최신이야기
사람이야기
회사이야기
기술이야기
다양한이야기
최신이야기
검색
회사이야기
[행복한 회사 만들기 TF] 꼰대와 어른의 차이
회사이야기
[행복한 회사 만들기 TF] 꼰대와 어른의 차이
<출처: 유튜브 '달리(SBS 교양 공식채널)'> “나… 꼰대인가?” “꼰대처럼 보이면 어쩌지?” 다들 한 번쯤 본인이나 상대가 꼰대인지에 대해 갑론을박해 본 경험 있으시죠? 기성세대 뿐만 아니라 MZ세대 역시 한 번쯤 생각해 본 질문일 텐데요. 최근에는 기성세대 뺨치는 ‘젊꼰(젊은 꼰대)’이 화제입니다. 사람인 설문조사에 따르면, 약 40% 정도가 “주위에 20~30대 역꼰대가 있다”고 응답했다고 해요. 누구든 이 꼰대라는 단어를 듣기 좋아하는 분은 단 한 명도 없을 텐데요. ‘꼰대’는 상황이나 사람에 따라 정의가 달라지는 것 같습니다. 그래서 ‘YB TF’ 2기는 꼰대에 대한 정의를 내려보고, 회사 생활을 하며 경험했던 꼰대 사례를 찾아봤습니다. YB가 정의한 꼰대는 ‘자기중심적으로 사고하는 사람’입니다. 상급자는 자신의 권위를 인정받기 위해, 하급자는 사적인 영역을 보호하려다 각각 자기 중심적으로 행동하게 되는 것 같다는 의견이 오갔습니다. 다르게는 자기 생각만 강요하거나 충고를 하고, 반대로 타인의 조언과 걱정은 무시하는 사람을 말하기도 합니다. 다행히도 브레인즈컴퍼니에는 동료들과 공유할 만한 심각한 꼰대 사례는 없었습니다. 상사 잘 만나는 것은 3대가 덕을 쌓아야 가능하고, 후배 잘 만나는 것은 5대가 덕을 쌓아야 한다는 말이 있는데요. 브레인저들은 모두 조상신이 도왔나봐요. 꼰대 사례 대신 YB의 관심을 끈 의견은 따로 있었는데요. 바로, “오히려 꼰대 소리를 듣기 싫어서 동료의 잘못된 행동을 보고도 충고를 하지 못했다”라는 내용이었습니다. 꼰대 소리를 듣지 않으려고 더욱 조심하고 말을 줄이다 보면, 혹시나 서로 불편해지고 멀어지지 않을까하는 염려가 되는데요. 이를 예방하고 행복한 회사를 만들기 위해, 선후배 사이에 서로를 어떻게 바라보는게 좋을지 알아봤습니다. 먼저, 후배에게 사랑받는 선배가 되는 꿀팁입니다. 첫째로, 더하기 보다 빼기! 무언가 더해주려고 애쓰기보다 덜어주려는 ‘빼기의 기술’이 더 중요합니다. 고생하는 후배에게 식사 시간 마다 맛있는 음식을 사줄 때도 있겠지만, 간혹 후배들끼리 편한 시간을 갖도록 빠져주는 센스를 발휘해 보는 것은 어떨까요? 어느 설문조사에 따르면, 회식자리에서 가장 멋진 상사는 ‘카드만 건네 주고 빨리 빠져주는 상사’이고, 기립박수 받는 상사는 "최근에 많이 노력해줘서 감사합니다. 간단하게 마시고 어서 집에 가서 쉽시다"라고 짧게 말하는 상사라고 하네요. 업무를 가르쳐 주는 상황에서도 빼기의 기술은 필요합니다. 정말 중요한 업무는 오히려 후배 스스로 방법을 깨울 칠 때까지 기다려주세요. 너무 잘 해야겠다는 의지로 불타오르는 후배가 있다면 그 열정, 조금 식혀주세요. 길고 긴 직장 생활을 더 슬기롭게 헤쳐 나가기 위해 힘빼기의 중요성을 알려주세요. 둘째, 단정짓지 말고 공감해주세요. 자신이 틀릴 수도 있다는 것을 인정하는 것이 공감의 출발점입니다. ‘Young Brainz’ TF에서 나온 사례 중, “하던 일이 마무리되지 않았음에도 쿨하게 퇴근하는 후배의 모습을 이해하기 어렵다”는 의견이 있었는데요. 과거 근면성이 중시되는 시절의 관점에서는 무책임하고 불성실해 보일 수도 있지만, 이제는 삶과 일을 대하는 방식에 있어 다양성이 존재하므로 이를 인정하고 포용하는 자세를 가져야 할 것 같습니다. 이후 일에 있어 시점관리가 왜 중요한지에 대해 후배의 생각을 들어보면 좋을 것 같아요. 셋째, 선배의 품격은 여유에서 나온다고 합니다. 요즘 후배들은 체계적으로, 게다가 많이 배워서 인지 일처리가 빠릅니다. 어떤 선배는 “후배에게 뒤처지면 어쩌나”하는 불안감이 생긴다고 해요. 이런 불안감에 방어기제가 작동해 후배의 생각에 귀 기울일 여유를 잃어버리고, 후배들 제각각이 지닌 능력과 가치도 무시하게 되는 경향을 띈다고 하니, 여유를 가져보는 게 어떨까요? 다음은 후배들을 위한 꿀팁입니다. 첫째, 질문할 때는 센스 있게! 무언가 맡겨 놓은 물건을 찾는 듯한 태도로 질문하면 선배의 입은 닫히고 맙니다. 질문은 대답을 요구하는 것으로, 대화 및 인간 관계의 시작입니다. 따라서 좋은 질문은 좋은 인간관계를 만들어줍니다. 좋은 질문을 위해서는 약간의 예의가 동반돼야 합니다. 정중한 자세로 질문하는 것이 좋지만, 그렇다고 너무 딱딱한 자세는 오히려 역효과가 날 수 있습니다. 차라리 장난끼가 있더라도 부드러운 분위기 조성이 중요한 것 같아요. 둘째, 선배를 춤추게 하는 칭찬! 칭찬을 좋아하지 않는 사람이 있을까요? "이번 일에 선배님이 너무 많은 도움을 주셨어요. 정말 감사했습니다!", "선배님이 있으니 너무 든든해요!" 오버하는 것... 맞지만, 누구든 이런 이야기를 들으면 기분이 좋아지고 후배를 한번 더 챙겨주게 된다고 하네요. 셋째, 실력있는 후배가 사랑받는 후배! MZ세대는 그 세대의 특징에 굉장한 자부심이 있다고 합니다. 하고 싶은 말을 시원하게 하고, 자기한테 손해가 가는 상황이라고 판단되면 강하게 반발하기도 합니다. 반면에 이런 자기들의 모습을 이해 못하고, 불합리한 상황에 가만히 수긍하는 선배들에게 불만을 느낀다고 해요. 불만 뒤에 숨어있지 말고 번뜩이는 창의력을 발휘해 선배보다 낫다는 것을 보여주세요. 실력 있는 후배가 사랑받는 후배입니다. 이상으로 선후배 모두에게 도움될 만한 꿀팁들을 정리해봤습니다. 사랑받는 동료가 되는 지혜는 회사에서만 필요한 것은 아닐 겁니다. 가정에서 부모로, 자식으로 사랑받는 지혜도 동일할 겁니다. 마지막으로 자료를 찾다가 도움이 될 만한 명언 소개로 마무리하겠습니다.
2022.11.28
회사이야기
[행사] CEO가 쏜다!
회사이야기
[행사] CEO가 쏜다!
“브레인즈컴퍼니의 가장 큰 자산은 브레인저” 브레인즈컴퍼니 CEO 선근님의 경영 철학 중 하나입니다. 선근님은 직원이 없으면 브레인즈컴퍼니도 존재할 수 없다는 생각을 바탕으로, 브레인저들이 더욱 만족하며 행복한 회사생활을 할 수 있도록 항상 고민합니다. 이번 ‘CEO가 쏜다’ 역시 그 일환으로 진행되는 행사 중 하나입니다. 앞서 소개해드렸던 ‘BB데이’와 ‘브행시’가 타 부서 간 소통을 위한 단체 행사라면, ‘CEO가 쏜다’는 소규모로 조금 더 친밀한 관계를 형성해 나가기 위한 행사입니다. 성수역 카페거리에 위치한 이점을 활용해, 선근님과 브레인저들이 2주에 한 번씩 점심시간에 맛집 탐방을 하고 있습니다. 첫 ‘CEO가 쏜다’는 주니어로만 구성됐던 ‘행복한 회사 만들기 TF, YB(Young Brainz)’ 1기와 진행했습니다. 이탈리안 음식점 ‘보이어’에서 점심 코스를 먹고, 드라마 빈센조 촬영지로 유명한 카페‘할아버지 공장’에서 디저트를 먹고 마무리했습니다. 이 외에도, 프리미엄 돼지고기 전문점 ‘메종 성수돈’을 비롯해 브런치 전문점 ‘메이플탑’, 베트남 음식 전문점 ‘꽌코이’ 등 다양한 맛집 투어를 해왔습니다. 최근에는 퓨전 음식 전문점 ‘이오로비스트로’를 방문했습니다. 식당에 들어서자마자 점심 회식 중이던 인프라코어팀을 만났습니다. 그냥 보고 지나칠 수 없었던 선근님! 자리에 앉기도 전에 인프라코어팀 회식부터 결제했어요. 이 기회에 선근님 자랑을 살짝 해보자면, 훈훈한 외모에 동갑인 유재석 뺨치는 입담과 더불어, 식사 때 브레인저가 메뉴 선택에 눈치보지 않도록 본인은 마지막에 주문하는 센스까지 겸비하고 있습니다. 또, 브레인저와 편하게 수다 떠는 것도 좋아해 고민이 있는 직원들은 선근님에게 상담을 받기도 해요. 일할 때는 철두철미하고 이성적인 모습을 보여주지만, 평소에는 취미로 기타 연주 및 노래를 즐기는 등 감성 충만한 면도 있습니다. 항상 브레인저와 소통하려는 선근님의 노력 덕분에 ‘2022년 상반기 회사 만족도 조사’에서 CEO 지지율이 약 90%로 나타났습니다. 또, 브레인저 절반 이상이 수평적인 문화와 자유로운 분위기, 복지와 성장 등을 위한 노력을 브레인즈의 장점으로 꼽았습니다! 이처럼 브레인저들은 CEO와 함께 행복한 회사를 만들어 가고 있습니다. 직장인들이 하루 중 가장 많은 시간을 보내는 곳, 그곳으로 향하는 발걸음이 무겁지 않도록 브레인즈컴퍼니는 오늘도 노력하고 발전해 나가고 있습니다. 행복하게 회사 생활을 하고 싶은 분들이 있다면, 주저하지 말고 합류하세요!
2022.11.18
기술이야기
[통합로그관리] Filebeat에서 안정적으로 하드웨어 자원 사용하기
기술이야기
[통합로그관리] Filebeat에서 안정적으로 하드웨어 자원 사용하기
Filebeat는 Elastic Stack에서 사용하는 경량(light-weight) 데이터 수집기로 logstash 대비 상대적으로 리소스(CPU와 RAM)를 상당히 적게 소모한다는 장점이 있습니다. 또, Filebeat는 간단한 필터 기능도 제공합니다. 하지만 말 그대로 간단한 필터 기능이라 한번에 대용량의 파일을 관리해야 하는 경우 호스트 서버에 부담이 갈 정도로 많은 리소스를 사용할 수 있습니다. 따라서 브레인즈컴퍼니가 운영하는 통합로그관리 에이전트는 호스트의 서버 환경에 따라 filebeat 에이전트의 설정 파일을 수정해서 안정성을 제공하고 있습니다. 본 내용은 Filebeat 리소스 점유율이 높을 때 트러블슈팅 관련 설정 수정사항입니다. 수정에 필요한 기본 파일 위치 linux : /etc/filebeat/filebeat.yml docker: /usr/share/filebeat/filebeat.yml filebeat 프로세스 메모리 확인하는 방법 top -d 1 | egrep "PID|filebeat" 수정에 앞서 filebeat의 메인 컴포넌트인 harvester의 개념을 간략하게 설명하겠습니다. 하나의 harvester는 하나의 파일을 읽어드립니다. harvester가 실행 중인 경우 파일을 한 줄씩 읽습니다. 각 파일 당 하나의 harvester가 실행됩니다. 상단의 이미지를 보면 filebeat의 컴포넌트인 input과 harvester가 보입니다. 또한 filebeat이 harvester를 관리하며 어느 파일을 읽을지 관리하는걸 알 수 있습니다. harvester가 실행 중인 경우 파일 설명자(File Descriptor) 열린 상태로 유지됩니다. 이는 파일이 삭제되거나 파일명이 변경된다 하더라도 파일을 계속 읽게 해줍니다. 하지만 파일 설명자는 harvester가 닫힐 때까지 디스크 공간을 예약합니다. 1. filebeat.inputs: 2. - type: filestream 3. id: my-filestream-id 4. paths: 5. - /var/log/system.log 6. - /var/log/wifi.log 7. - type: filestream 8. id: apache-filestream-id 9. paths: 10. - "/var/log/apache2/*" 11. fields: 12. apache: true 13. fields_under_root: true <filebeat에서 제공하는 input example> 1. scan_frequency 파일비트가 설정된 filebeat_inputs의 path에 있는 파일들의 갱신 여부를 체크하는 주기입니다. 너무 길게 설정하면 한번에 많은 파일들을 수집하게 됩니다. 반대로 너무 짧게 설정하면 스캔을 너무 잦게 해서 CPU점유율이 올라갑니다. 적당한 조절이 필요합니다. 기본값은 10초입니다. Scan_frequeny가 동작하는 방식은 아래와 같습니다. harvester 읽기 종료 또는 파일 삭제 → scan_frequency 만큼 대기 → 파일 갱신 확인 → 파일 갱신 시 새 harvester 시작 2. backoff Backoff 옵션은 파일비트가 얼마나 더 적극적으로 크롤링 하는지 지정합니다. 기본값은 1인데 1일 경우 새 줄이 추가될 경우 1초마다 확인한다는 의미입니다. Backoff가 동작하는 방식은 아래와 같습니다. harvester 읽기 종료 또는 파일 삭제 → scan_frequency만큼 대기 → 파일 갱신 확인 → 파일 갱신 시 새 harvester 시작 → 파일 갱신 시 Backoff 시간 마다 다시 확인 3. max_procs 파일비트에서 동시에 사용 가능한 최대의 cpu코어의 숫자를 설정합니다. 예를 들어32 CPU코어 시스템에서 max_procs를 1로 설정한다면 cpu사용률은 3.2%(1/32)를 넘지 않습니다. max_procs 설정돼 있으면 harvester가 아무리 많이 생성돼도 cpu의 코어 수만큼 CPU를 점유합니다. 4. harvester_limit harvester의 수가 OS가 감당할 수 있는 파일 핸들러 개수를 초과할 때 사용합니다. 한 input마다 설정되므로 inputs이 5개 선언돼 있으면 해당 input 컴퍼넌트의 harvester 개수 최대치는 5개입니다. 기본값은 0인데, 0일 경우 harvester가 무제한으로 생성 가능합니다. 리소스 관리 최적화에도 유용한데 예를 들어, input1이 input2보다 파일 개수가 3배 많고 중요성이 높을 때 3배 높은 값을 설정하는 것이 좋습니다. 5. close_eof harvester에 의해 파일이 수집되고 있을 때, EOF(End of File)에 도달하는 즉시 파일을 닫습니다. 파일이 계속 갱신된다면 데이터가 유실될 수 있는 여지가 있습니다. [참조] https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html
2022.11.17
기술이야기
통합로그관리가 필요한 3가지 이유
기술이야기
통합로그관리가 필요한 3가지 이유
로그는 IT 인프라에서 발생하는 모든 상황들을 기록한 데이터입니다. 쉽게 말해 사용자가 어떤 루트로 사이트에 접속했고, 접속한 시점부터 어떤 행동을 취했는지가 모두 기록으로 남게 되는데, 이 기록들이 로그입니다. 로그는 IT 환경에서 가장 많이 발생하지만, 데이터 처리 기술이 발달하지 않았던 시기에는 처리 비용에 비해 가치가 낮은 데이터로 여겨졌습니다. 하지만 최근들어 IT 서비스와 인프라가 다양해지고 디지털 트랜스포메이션이 가속화되면서, 로그의 양이 기하급수적으로 증가하고 사물인터넷(IoT), 빅데이터 등과 같은 신기술이 발전하면서 그 효용성 또한 날로 증가하고 있습니다. 그렇다면, 이 로그는 실제로 어떻게 활용될까요? 개발 영역에서는 버그 혹은 크래시율 수집 및 상시 트래킹, 이슈 발생 후 롤백 및 대응, 특정 기능에 대한 사용성 진단에 활용됩니다. 마케팅 분야는 채널별 ROI 진단 및 비용 최적화, 배너/프로모션/이벤트 효과 측정, 유저 세그멘테이션 및 타게팅에 사용합니다. 기획 및 디자인 영역은 기능 개선을 위한 A/B 테스트, 유저 Journey 경로 분석을 통한 UX/UI 최적화 등에서 쓰이고 있습니다. 이처럼 여러 영역에서 다양하게 쓰이는 로그를 관리하지 않고 방치해두면 어떤 일이 발생할까요? 통합로그관리가 필요한 이유에 대해 알아보겠습니다. ----------------------------------------------- I. 보안 대응체계 구축 저장만 하고 관리되지 않은 로그는 IT 시스템의 장애나 문제 발생 시 그 원인을 찾아내기가 어렵습니다. 또, 로그 데이터의 중요 정보가 외부로 유출될 위험도 커집니다. 끊임없이 발생하는 보안 사고에 대비하기 위해 통합로그관리는 반드시 필요합니다. 관리된 로그는 장애나 사고 발생 시에 그 원인을 파악하고 빠른 대처를 위한 근거 데이터로 사용할 수 있으며, 보안 체계를 마련하는 데에도 활용가능 합니다. 기업들은 로그관리 제품을 사용해 사이버 침해위협을 예방 및 감시하고, 정기적인 로그분석을 통해 강력한 보안대응체계를 구축하고 있습니다. 통합로그관리 솔루션은 보안장비(Firewall, IDC, IPS 등)의 로그와 해킹, 악성코드 등 보안/침해 관련 로그를 지속적으로 분석해 예방 체계를 구축합니다. 또, 대용량 로그의 상관분석을 통해 보안위협을 탐지하고 이상징후를 모니터링하는 등 강력한 보안 대응체계를 구축할 수 있습니다. II. 컴플라이언스 준수 로그는 보안 사고가 발생했을 때 가장 기본적인 증거 및 모니터링 자료로 활용됩니다. 이에 따라 정부에서는 데이터 관리에 대해 각종 법률을 규정하고 있어, 공공기관을 비롯한 개인정보를 다루는 온라인 사업자 및 기업 등은 해당 법규를 준수해야 합니다. 안전한 데이터 이용을 위해 2018년에 발의된 '데이터 3법' 개정안은 2020년 1월 9일 국회 본회의를 통과했습니다. 데이터 3법은 개인정보 보호법, 정보통신망 이용촉진 및 정보보호 등에 관한 법률, 신용정보의 이용 및 보호에 관한 법률 등 3가지 법률을 통칭합니다. 로그 관리 관련 규제의 주요 내용은 다음과 같습니다. i. 개인정보보호를 위해 접근 권한 부여, 변경 또는 말소 기록을 3년 이상 보관해야 합니다. ii. 개인정보 취급자는 개인정보처리시스템의 접속기록을 월 1회 이상 점검해야 하고, 그 활동의 증거를 남기기 위해 시스템에 접속했다는 기록을 1년 이상 보관해야 합니다. iii. 정보통신서비스 제공자는 접근 권한 내역을 5년간 보관하고, 접속 기록의 위·변조 방지를 위해 반드시 백업 보관해야 합니다. III. 빅데이터 처리 플랫폼 IT 인프라 확대 및 기타 비정형 로그 유입에 따라 대용량 로그에 대한 관리가 요구되고 있습니다. 특히 수집된 로그를 실시간으로 분석∙판단해 IT 서비스의 안정적 운영을 도모해야 하는 수요가 증대되고 있는데요. 오늘날의 데이터는 기존 데이터에 비해 양이 매우 방대해 기존 방법이나 도구로는 관리가 어렵습니다. 따라서 빅데이터 기술을 기반으로 하는 대용량 통합 로그관리 솔루션은 이제 IT 운영을 위한 필수 솔루션으로 자리잡았습니다. ----------------------------------------------- 이처럼 기업은 보안위협 및 이상징후 대응/컴플라이언스 준수/대용량 로그 관리를 위해 통합로그관리 솔루션을 필수로 갖춰야합니다. 브레인즈컴퍼니의 통합로그관리 솔루션 '제니우스(Zenius) Logmanager'는 이기종 장비에서 발생되는 정형∙비정형 로그 데이터의 수집/분석/관리 등을 위한 빅데이터 플랫폼입니다. 제니우스 로그매니저가 어떻게 구성돼 있는지 살펴보겠습니다. 제니우스 로그매니저는 정형/반정형 또는 비정형 로그에 대한 실시간 수집 및 신속한 분석 기능을 제공하며, 이러한 정보들을 다양한 차트와 대시보드를 통해 직관적으로 가시화합니다. 특히 로그매니저는 독보적인 인덱싱 및 검색 속도를 제공하며 확장성, 편의성, 효율성, 호환성 등의 특장점을 보유한 제품입니다. 로그 이벤트 발생 시 즉각적인 알람을 통해 빠른 문제 해결과 높은 가용성을 확보하도록 지원합니다.
2022.11.10
회사이야기
브레인저가 되면 누릴 수 있는 것들 ㅣ (2) 아침식사 편
회사이야기
브레인저가 되면 누릴 수 있는 것들 ㅣ (2) 아침식사 편
오늘 아침, 식사는 하셨나요? 직장인들이 바쁜 아침 시간에 식사를 든든히 챙겨 먹고 출근하기란 참 힘든 일인 것 같습니다. 밥 보다는 잠을 선택하는 분들도 많을 텐데요. 출근 후에는 공복에 커피를 마시는 경우가 다반사라, 직장인들의 건강 관리가 절실해 보입니다. 브레인즈컴퍼니는 브레인저들이 하루를 활기차게 시작하고, 건강한 생활을 유지할 수 있도록 아침식사를 제공하고 있습니다. 브레인즈컴퍼니의 아침은 8층 라운지에서 제공하며, 우유, 주스, 탄산수 등 다양한 무료 음료와 더불어 커피머신으로 바로 내린 커피와 함께 섭취할 수 있습니다. 아침식사 메뉴는 월∙금 김밥, 화∙수∙목은 샌드위치/유부초밥/볶음밥/주먹밥 등 랜덤으로 제공되고 있습니다. 회사에서 제공되는 아침은 직원 건강뿐만 아니라 생산성과도 직결되는데요. 직접 아침을 차려 먹지 않아도 되니, 출근 준비가 수월해지고 지각이 줄어드는 효과가 있습니다. 또, 타 부서 직원들과 함께 아침을 먹을 수 있어, 부서 간 장벽을 낮추고 사내 소통에도 유용합니다. 아침식사는 브레인저들이 가장 만족도가 높은 복지 중 하나인데요. 매일 아침을 챙겨 먹는 한 브레인저는 “매일 다른 메뉴로 아침을 먹을 수 있어 좋아요. 동료들과 같이 식사를 하면서 어제 퇴근 후에 뭐했는지, 요즘 어떤 취미가 있었는지 등 서로를 알아가는 재미도 쏠쏠하고요. 간혹 혼자 좋아하는 유튜브를 보면서 먹기도 해요. 그리고 카페테리아에 식사와 함께 할 수 있는 원두 커피나 각종 음료가 준비돼 있어 더 좋은 것 같아요.”라고 전해왔습니다. 이처럼 브레인저들은 “먹거리는 부족해선 안된다”는 선근님의 철칙 덕분에, 항상 든든하게 하루를 시작하고 있습니다. 참고로 아침뿐만 아니라 행사가 있는 날이면 항상 음식을 넉넉하게 주문해, 출출한 오후 시간에 브레인저들의 간식으로도 활용하고 있습니다. 이 글을 읽고 있을 예비 브레인저분들! 브레인즈컴퍼니에 합류해 첫 출근하는 날, 꼭 8층 라운지에 들러 아침식사 챙겨 드세요. 냉장고에 있는 음료도 편하게 꺼내 드시면 돼요.
2022.11.09
회사이야기
브레인저가 되면 누릴 수 있는 것들 ㅣ (1) 근무환경 편
회사이야기
브레인저가 되면 누릴 수 있는 것들 ㅣ (1) 근무환경 편
브레인즈컴퍼니의 사명은 BRAINZ와 COMPANY 두 단어가 결합해 만들어졌습니다. BRAINZ는 ‘학식과 경험이 풍부한 전문가들’을 의미하고, COMPANY의 기원은 '함께 빵을 나누는 사이'라는 뜻을 담고 있습니다. 즉, 브레인즈컴퍼니는 '전문가들이 식구처럼, 함께 잘 살아 보기 위해 만든 조직'이라는 의미를 지니고 있는데요. 선근님(브레인즈컴퍼니 대표)은 "회사의 가장 큰 자산은 브레인저”라는 가치관을 바탕으로, 브레인저와 함께 잘 살아보기 위해 끊임없이 고민하고 있습니다. 그 중, 회사의 전부인 브레인저들이 하루 중 가장 많은 시간을 함께 보내는 공간만큼은 불편함 없이 최적화된 환경에서 지낼 수 있도록 했습니다. 브레인저들이 함께 얼굴 맞대며 살아가고 있는 공간들, 함께 보러 가실까요? 한국의 브루클린, 성수 “젊은이들은 왜 성수동을 좋아하는가? 그곳에는 그들이 체험해보지 못한 공간이 있기 때문이다. 성수동은 예로부터 자동차 수리공장 같은 크고 작은 공장이 위치한 곳이다. 이런 공장들은 넓은 공간이 필요하기 때문에 필지가 300평가량으로 나뉘어져 있고 기둥 상의 간격도 넓고 천정고도 높다. 이러한 중간 크기의 공간은 서울의 다른 곳에서는 찾기 힘든 공간구조다.” <중앙SUNDAY-일터, 판교 대신 성수동 선호한다는데> 브레인즈컴퍼니는 한국의 브루클린이라 불리는 성수, 그 중에서도 핫하다는 카페거리에 위치해 있습니다. 성수역 3번 출구에서 200m 남짓한 거리에 있어, 교통이 매우 편리하고 힙한 공간들이 많습니다. 이러한 공간을 활용해, 선근님과 함께 브레인저들이 주변 맛집을 자주 탐방하고 있고 사내 행사 때도 항상 맛있는 음식들로 채워지고 있습니다. 브레인저가 가장 즐겨 찾는 공간은? 브레인즈컴퍼니 8층에는 브레인저들이 출근 하자마자 찾는 공간이자 근무 중 가장 즐겨 찾는 공간이 있습니다. 바로 카페테리아인데요. 카페테리아는 브레인저의 건강을 위해 아침식사를 제공하는 공간이기도 하고, 근무시간 중에는 커피와 음료, 간식 등을 무료로 먹으며 편하게 쉴 수 있는 공간입니다. 카페테리아 바로 옆에는 미니 도서관이 있는데요. 브레인저가 원하는 책을 구매해주고 함께 공유하고 있습니다. 빈백과 쿠션이 구비돼 있어 편하게 드러누워 책을 읽거나 잠을 청할 수도 있는 공간입니다. 따뜻한 사연이 담긴 공간 브레인저가 50명도 채 안 되던 시절, 선근님은 출산 후 복직한 여직원이 화장실에서 유축했다는 이야기를 듣고는 충격을 받았습니다. 실행력이 강한 선근님은 당장 여직원을 위한 휴게실을 만들라는 지시를 내렸고, 일주일만에 완성됐다고 합니다. 현재 이 공간은 유축 외에도 여직원들이 한숨 자거나 식사를 하는 공간으로도 활용되고 있습니다. 선근님의 애틋한 마음이 담긴 공간, 함께 구경해 보시죠. 일하는 장소 브레인즈컴퍼니는 4개 층을 사용하고 있습니다. 각 층별로 사이즈와 구조, 인테리어 등은 조금씩 다르지만, 라커와 회의실, 공기청정기, 넓은 책상, 베리 데스크와 같은 편의 시설은 공통적으로 갖추고 있습니다. 입구에 설치된 라커는 각자의 좌우명이 새겨져 있고, 일하는 공간을 불편함 없이 사용할 수 있도록 가방과 겉옷 등 짐을 넣어 둘 수 있도록 만들었습니다. 개인 업무공간은 모니터 두대를 놓고도 넉넉한 책상과 푹 기댈 수 있는 편안한 의자로 구성돼 있습니다. 앉아만 있어 허리가 불편한 브레인저를 위해서 높이가 조절되는 베리 데스크도 제공합니다. 각 층마다 여러 대의 공기청정기도 잊지 않고 배치해 뒀습니다. 브레인즈만의 공간, 어떠셨나요? 이 외에도 동료를 배려하는 전화부스, 다양한 크기의 회의실 등의 공간이 있습니다. 브레인즈컴퍼니에서 가치있는 브레인저로서 함께 성장해 나갈분들은 주저하지 말고 합류해주세요!
2022.10.21
기술이야기
Java APM 기반 기술에 대한 간략한 설명
기술이야기
Java APM 기반 기술에 대한 간략한 설명
몇 년 전부터 미국 실리콘밸리에서 불어온 스타트업 광풍이 인플레이션과 경기 침체가 동시에 예상되는 최악의 전망 속에서 조금 사그러드는 모습입니다. 그러나 빠른 속도로 퍼지기 시작한 IT 관련 유행들은 아마 꽤 오랜 시간 우리들 근처에 남아 그 영향이 지속되지 않을까 예상해봅니다. 그 중 한 부분을 차지하는 것이 새로운 혹은 인기가 급상승한 Go, Python, R, Julia, Kotlin, Rust, Swift 등의 컴퓨터 언어들입니다. 이렇게 많은 언어들이 새로 등장해 번쩍번쩍하는 장점을 뽐내고 있는 와중에도, 아직 세상의 많은 부분, 특히 ‘엔터프라이즈 IT’라 불리는 영역에서 여전히 가장 많이 사용되는 것은 Java입니다. 절대적이지는 않지만 컴퓨터 언어의 인기 순위 차트인 TIOBE 인덱스에 따르면, 2022년 6월 현재도 Java의 인기는 Python, C의 뒤를 잇는 3위입니다. Java 역시 Java 9부터는 십 수년간 고수하던 백워드 컴패티빌리티 정책을 포기하고 여러가지 반짝거리는 장점을 받아들이면서 버전업을 계속해, 올해 9월에는 Java 19가 나올 예정입니다. 그러나 아직도 우리나라 ‘엔터프라이즈 IT’에서 가장 많이 쓰이는 버전, 그리고 작년까지는 세계에서 가장 많이 쓰이는 버전은 Java 8이었습니다. 이렇게 많은 Java 어플리케이션의 성능을 모니터링하고 관리할 수 있는 솔루션을 통상적으로 APM(Application Performance Management)이라고 합니다. 위에서 서술한 것처럼 다른 컴퓨터 언어들의 인기가 올라가고 사용되는 컴퓨터 언어가 다양해지면서 많은 APM 제품들이 Java외의 다른 컴퓨터 언어로 작성된 어플리케이션도 지원하는 경우가 늘어나고 있으나, 이 글에서는 APM을 Java 어플리케이션의 성능을 모니터링하고 관리할 수 있는 솔루션으로 한정하도록 하겠습니다. 어플리케이션의 성능을 보다 깊이 모니터링하는데 필수적인 것이 Trace[i]입니다. Trace는 어플리케이션이 실행되는 과정에 중요하다고 생각되는 부분에서 중요하다고 생각되는 어플리케이션의 상태를 기록으로 남긴 것입니다. 전통적인 어플리케이션에서는 실행 Thread를 따라가면서 순차적인 Trace가 남게 되고 유행에 맞는 MSA(Micro-Service Architecture) 어플리케이션에서는 서로 연관됐지만 직선적이지는 않은 형태의 Trace가 남게 됩니다. 이러한 Trace를 수집하고 추적하고 분석하는 것이 APM의 주요 기능 중 하나입니다. 그런데, 여기서 문제가 하나 생깁니다. Trace는 누가 남길 것인가 하는 문제입니다. 개발 리소스가 충분하고 여유가 있는 경우, 개발시 성능에 대한 부분에 신경을 써서 개발자들이 Trace를 남기며 이를 분석하고 최적화하는 것이 정례화, 프로세스화 돼있겠지만, 많은 경우 개발 리소스를 보다 중요한 목표 달성을 위해 투입하는 것도 모자랄 지경인 것이 현실입니다. 아무리 분석 툴인 APM이 좋아도, 분석할 거리가 되는 Trace가 없으면 무용지물이 돼 버립니다. 그래서 APM에는 미리 정해진 중요한 시점에 어플리케이션에서 아무 것도 하지 않더라도 자동으로 Trace를 남기도록 하는 기능이 필수적으로 필요합니다. Java 어플리케이션의 경우 이러한 기능은 Java Bytecode Instrumentation이라고 하는 기반 기술을 사용해 구현됩니다. 서론이 매우 길어졌지만, 이 글에서는 Java Bytecode Instrumentation에 대해 조금 상세히 살펴보도록 하겠습니다. Java Bytecode Instrumentation을 명확히 이해하려면, 먼저 Java가 아니라 C, C++, Rust등의 언어들로 작성된 프로그램이 어떤 과정을 거쳐서 실행되는가, 그리고 Java 프로그램은 어떤 과정을 거쳐서 실행되는가를 살펴보는 것이 도움이 됩니다. Java가 세상에 나오기 이전에는 ‘컴퓨터 학원’이나 고등학교 ‘기술’ 과목, 그리고 대학의 ‘컴퓨터 개론’ 등에 반드시 이런 내용이 포함돼 있었지만 요즘은 그렇지도 않은 것 같습니다. 컴퓨터에서 프로그램을 실행시키는 것은 CPU, 즉 Central Processing Unit입니다. 지금 이 글을 작성하고 있는 컴퓨터의 CPU는 Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz입니다. CPU는 메모리의 프로그램이 있는 영역을 읽어 들여, 미리 정해진 값에 따라 정해진 동작을 수행하게 됩니다. 이때 어떤 값이 어떤 동작을 수행하는지 규정해 놓은 것을 Machine Language라고 합니다. Machine Language는 100% 숫자의 나열이므로 이를 좀더 사람이 읽기 쉬운 형태로 1:1 매핑 시킨 것이 Assembly Language입니다. (그렇다고 읽기가 많이 쉬워지지는 않습니다.) 이 글에서는 이 두 단어를 구분없이 혼동해 사용합니다. C, C++, 그리고 나온 지 벌써 10년이나 된 Go, 요즘 인기가 계속 상승하고 있는 Rust 등의 언어로 작성된 프로그램은, 이들 언어로 작성된 소스 코드를 Machine Language로 미리 변환해서[ii] 실행 파일을 만들고 이를 실행하게 됩니다. 이 변환을 수행하는 것을 Compile한다라고 하고 이 변환을 수행하는 프로그램을 Compiler라고 부릅니다. 한편, 소스 코드를 완전히 Machine Language로 변환시킨 실행 파일을 실행하는 것이 아니라 Interpreter라 불리우는 프로그램이 소스 코드를 읽으면서 그 의미에 맞게 동작을 수행시키는 언어들도 있습니다. ‘스크립트 언어’라 불리는 bash, Perl, PHP, Ruby, Python 등이 이에 해당되면, 요즘은 잘 쓰이지 않지만 그 옛날 Bill Gates가 직접 Interpreter를 만들기도 했던 BASIC 등이 이에 해당합니다. 본론으로 돌아가보겠습니다. 그렇다면, Java 프로그램은 어떤 방식으로 실행이 되는가? 기본적으로는 Interpreter 방식이라고 생각해도 이 글의 주제인 Java Bytecode Instrumentation을 이해하는 데는 무리가 없습니다.[iii] 여기에 더해 Java의 실행 방식에는 몇 가지 큰 특징이 있습니다. 첫째로, Java는 소스 파일을 직접 읽어 들이면서 실행하는 것이 아니라 소스 파일을 미리 변환시킨 Java Class File을 읽어 들이면서 실행합니다. 하나의 Java Class File에는 하나의 Java Class 내용이 모두 포함됩니다. 즉, Class의 이름, public/private/internal 여부, 부모 클래스, implement하는 interface 등의 Class에 대한 정보, Class의 각 필드들의 정보, Class의 각 메서드[iv]들의 정보, Class에서 참조하는 심볼과 상수들, 그리고 이 글에서 가장 중요한 Java로 작성된 각 메서드의 내용을 Java Bytecode 혹은 JVM Bytecode라고 하는 중간 형태의 수열로 변환시킨 결과 등이 Java Class File에 들어가게 됩니다. 이 Java Bytecode는 실제 실행 환경인 CPU 및 Machine 아키텍처에 무관합니다. 똑같은 Java 소스 코드를 Windows에서 Compile해 Java Class File로 만들건, Linux에서 Compile해 Java Class File로 만들건 그 내용은 100% 동일하게 되고 이 점은 C, C++, Rust 등 Compiler 방식의 언어와 큰 차이점입니다. Java의 가장 큰 마케팅 캐치프레이즈 “Write Once, Run Anywhere”는 이를 표현한 것입니다. 둘째, Java Bytecode는 일반적인 CPU의 Machine Language와 많은 유사점을 지닙니다.[v] 어찌 보면 Java Bytecode는 실제 존재하지는 않지만 동작하는 가상의 CPU의 Machine Language라고 볼 수 있는 것입니다. 이러한 이유에서 Java Class File을 읽어 들여 실행시키는 프로그램을 JVM이라고 (Java Virtual Machine) 부릅니다. Java 소스 파일을 Java Class File로 변환시키는 프로그램을 Java Compiler라고 부르며, 가장 많이 쓰는 Java Compiler는 JDK(Java Development Kit)에 포함된 javac라고 하는 프로그램입니다.[vi] JVM은 JDK에 포함된 java라고 하는 프로그램을 가장 많이 씁니다. 한편 사용 빈도는 그렇게 높지 않지만, Java Class File을 사람이 알아볼 수 있는 형태로 변환해서 그 내용을 보고 싶은 경우도 있습니다. 이런 일을 하는 프로그램을 Java Bytecode Disassembler[vii]라고 부르며, JDK에는 Java Bytecode Disassembler인 javap가 포함돼 있습니다. 혹은, Eclipse나 Intellij IDEA 같은 IDE에서 Java Class File을 로드하면 사람이 알아볼 수 있는 형태로 변환해 보여줍니다. Java Bytecode의 실제 예를 한번 살펴보도록 하겠습니다. 설명을 간단히 하기 위해, 클래스나 메서드 선언 등은 다 제외하고, 오직 메서드의 내용에만 집중하면, System.out.println(“Hello, World.”); 라는 Java 프로그램은 다음과 같은 Java Bytecode로 변환됩니다. (전통적으로 16진수로 표시합니다.) b2 00 0b 12 09 b6 00 0f b1 이를 javap를 사용해, 혹은 JVM Reference[viii]를 보고 좀더 사람이 보기 쉬운 형태로 표현하면 다음과 같습니다. 0: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #9 // String Hello World 5: invokevirtual #15 // Method java/io/PrintStream.println: (Ljava/lang/String;)V 8: return JVM Reference의 Chapter 7을 참고하면, Java Bytecode를 javap의 결과에 어떻게 대응되는지를 알 수 있습니다. javap의 결과를 조금 더 살펴봅시다. 먼저 콜론 앞의 숫자는 인스트럭션의 offset으로서 Bytecode 시퀀스의 0번째, 3번째, 5번째, 8번째를 의미합니다. 0번째의 getstatic은 그 다음 숫자에 해당하는 필드를 스택의 맨 위에 저장하도록 합니다. 3번째의 ldc는 “Hello, World”라는 상수값을 스택의 맨 위에 저장하도록 합니다. 5번째의 invokevirtual은 println 메서드를 호출하고, 8번째의 return은 메서드에서 리턴해 호출한 곳으로 실행을 넘깁니다. Java 프로그램은 (정확히는 Java 소스 코드로 작성된 프로그램을 Compile한 결과) 통상적으로 많은 수의 Java Class File로 이뤄집니다. JVM은 이러한 Java Class File을 한꺼번에 읽어 들이는 것이 아니라 실행을 하다가 필요한 순간이 되면 그 때 읽어 들입니다. JVM은 이 로딩 과정에 사용자가 개입할 여지를 남겨 뒀는데, 이것이 Java Bytecode Instrumentation입니다. 이에 대한 개요는 https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html에 설명돼 있습니다. 요약해서 설명하면 다음과 같습니다. (1)사용자는 미리 정해진 규약대로 Java Agent라는 프로그램을 작성하고 이를 JVM 실행시에 옵션으로 명기합니다. (2)JVM은 Java Class File을 읽어 들여서 JVM이 처리하기 좋은 형태로 저장하기 전에, 그 파일 내용을 Java Agent의 ClassFileTransformer 클래스의 transform 메서드[ix]에 전달합니다. (3)JVM은 Java Class File의 원래 내용이 아니라 (2)의 메서드가 반환하는 결과를 저장하고 실행합니다. 이 과정을 Java Bytecode Instrumentation이라고 합니다. 사용자는 Java Bytecode Instrumentation을 구현해, 즉 Java Agent를 잘 작성헤 무엇이든 원하는 바를 달성할 수 있는 것입니다![x] 이러한 Java Bytecode Instrumentation은 APM, 그리고 Aspect-Oriented Programming의 기반 기술이 됩니다. 우리나라에서 Java로 프로그래밍을 한다고 하면 누구나 다 알고 있을 것 같은 Spring Core의 핵심 요소 중의 하나가 Aspect-Oriented Programming입니다. 예를 들어 Spring에서 @Transaction 이라고 annotation된 메서드가 있으면, Spring은 그 메서드의 맨 처음에 transaction을 시작하는 코드, 정상적으로 return하기 직전에는 transaction을 commit하는 코드, 그리고 익셉션에 의해 메서드를 빠져 나가기 직전에는 transaction을 rollback하는 코드를 삽입해 주게 되는데 이를 Java Bytecode Instrumentation을 이용해 구현하는 것입니다. 그럼, Java Agent에 거의 무조건적으로 필요한 기능은 무엇일까요? Java Agent는 Java Class File 내용을 그대로 전달받기 때문에 이를 해석할 수 있어야 무언가를 할 수 있습니다. 불행히도, java 스탠다드 라이브러리에는 Java Bytecode를 직접 다루는 기능은 없습니다.[xi] 그래서 de facto standard로 사용되는 것이 asm이라는 라이브러리입니다. 이 라이브러리는 수많은 java 라이브러리와 어플리케이션에 포함돼 있습니다. 그러나 asm이 훌륭한 라이브러리이긴 하지만, 이를 직접 사용하려면 각 상황에 맞게 코드를 삽입하는 프로그램을 작성해서 사용해야 하므로 자유도가 떨어집니다. 그래서 Zenius APM에서는 asm을 사용하되 삽입될 코드를 설정 파일에서 지정할 수 있는 suji(Simple Universal Java Instrumentor)[xii]라고 이름 붙인 라이브러리를 직접 만들어 사용하고 있습니다. suji를 사용하면 yaml 형식의 설정 파일에서, 어떤 클래스의 어떤 메서드의 어느 부분에 삽입할 것인지에 대한 조건과 삽입될 코드를 yaml의 list 형태로 지정하는 것만으로 (이는 Lisp와 비슷한 방식으로, 이렇게 하면 파싱 과정을 생략하면서 쉽게 코드를 넣을 수 있습니다.) Java Bytecode Instrumentation을 손쉽게 처리할 수 있습니다. 예를 들어, Zenius APM에서 JDBC getConnection을 처리하기 위해서 다음과 같은 부분이 설정 파일에 포함돼 있습니다. JDBC.DataSource.getConnection: IsEnabled: true ClassChecker: [ HasInterface, javax/sql/DataSource ] MethodName: getConnection IsStatic: false IsPublic: true IsDeclared: false ReturnType: Ljava/sql/Connection; Locals: [ Ljava/lang/Object;, Ljava/lang/Object; ] AtEntry: - [ INVOKE, dataSourceGetConnection, l1, [] ] AtExit: - [ INVOKE, poolGetConnectionEnd, l2, [ l1, ^r, true ] ] - [ LOAD, l2 ] - [ CAST, Ljava/sql/Connection; ] - [ STORE, ^r ] AtExceptionExit: - [ INVOKE, endByException, null, [ l1, ^e ] ] 간략하게 설명하면, Class가 만약 javax.sql.DataSource를 implement하고 메서드가 스태틱이 아니고 public이면서 java.sql.Connection을 리턴하는 getConnection이라는 이름을 가진 경우에 메서드 시작 시, 리턴 시, 그리고 익셉션에 의해 메서드를 나갈 때 위의 예제에 규정된 코드를 삽입하라는 의미입니다. 이상으로 Java Bytecode Instrumentation에 대한 간략한 설명을 마칩니다. 다음에는 실제로 APM이 중점적으로 추적하고 분석하는 것은 어떤 것들인가에 대해 설명하겠습니다. -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [i] Sridharan, Distributed Systems Observability, O’Reilly, 2018의 Chapter 4. The Three Pillars of Observability 참조. 번역본은 없는 듯합니다. [ii] 이 외에 여러가지 과정을 거치지만 이 글의 목적과는 무관하므로 과감하게, 자세한 설명은 생략합니다. [iii] 실제로는 Java 프로그램이 100% 이렇게 interpret되어 실행되는 것은 아닙니다. 특정 메쏘드 혹은 메쏘드의 일부분이 자주 실행돼 interpret하는 것보다 미리 컴퓨터(=CPU)가 바로 실행할 수 있는 형태(=Machine Language)로 변환(=compile)해 놓는 것이 더 낫다고 JVM이 판단하는 경우, 미리 이런 변환 과정을 한번 거쳐 그 결과를 기억해 놓고, 그 기억된 결과를 컴퓨터(=CPU)가 바로 실행합니다. 이렇게 변환하는 과정을 Just-In-Time Compile 혹은 JIT라고 합니다. 또 이 때문에 JVM을 단순한 interpreter로 부를 수는 없는 것입니다. [iv] 국립국어원은 메서드가 맞는 표기라고 합니다. [v] 물론 많은 차이점도 지닙니다. (1) JVM은 register가 존재하지 않고 오로지 stack에만 의존한다. (2) JVM은 Class, Method의 개념을 포함하고 있지만 일반적인 범용 CPU에는 그런 상위 개념은 없습니다. [vi] 보통 IDE를 써서 개발을 하기 때문에, javac를 직접 사용하거나 Java Class File을 직접 다룰 일은 잘 없고, jar 파일이 이 글을 읽는 여러분에게 훨씬 더 익숙할 지도 모릅니다. Jar 파일은 그냥 zip으로 압축된 파일이니 그 압축을 한번 풀어 보길 바란다. 확장자가 class인 수많은 파일을 찾을 수 있을 것입니다. [vii] Assembly는 Assemble의 명사형이며, Assemble의 반대말은 Disassemble입니다. [viii] JVM에 대한 모든 것은 The Java Virtual Machine Specification에 나와 있습니다. 이 중 'Chapter 6. The Java Virtual Machine Instruction Set'를 참고하면 각각의 instruction에 대해 상세히 알 수 있습니다. [ix] https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html#transform-java.lang.ClassLoader-java.lang.String-java.lang.Class-java.security.ProtectionDomain-byte:A- [x] 쉽다고는 하지 않았습니다. 또 몇가지 제약 사항은 있습니다. [xi] 참고로 최근에는 asm을 대체할 수 있는 기능을 스탠다드 라이브러리에 넣을 계획이 진행되고 있습니다. https://openjdk.org/jeps/8280389 [xii] 명명이 아이돌 그룹 출신 모 여배우와 관계가 아주 없지는 않음을 조심스럽게 밝혀 둡니다.
2022.08.04
1
2
3
4
5