<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>sckwon770</title>
    <link>https://way-code.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 16 Jun 2026 04:16:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sckwon770</managingEditor>
    <image>
      <title>sckwon770</title>
      <url>https://tistory1.daumcdn.net/tistory/3175283/attach/17252a404d3f45ceb562b4e5ffe166c3</url>
      <link>https://way-code.tistory.com</link>
    </image>
    <item>
      <title>Caused by: org.redisson.client.RedisAuthRequiredException: NOAUTH Authentication required. 원인 및 해결방법</title>
      <link>https://way-code.tistory.com/entry/Caused-by-orgredissonclientRedisAuthRequiredException-NOAUTH-Authentication-required-%EC%9B%90%EC%9D%B8-%EB%B0%8F-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 팀원들한테 테스트 서버가 접속이 안된다고 연락이 왔다. 분명 어제도 배포 workflow가 동작하고 정상 작동하는 것을 확인했기 때문에 이상해서 로그를 확인했더니, 처음 보는 로그가 있었다. 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1727595526025&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: org.redisson.client.RedisAuthRequiredException: NOAUTH Authentication required.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NOAUTH Authentication required. 란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NOAUTH Authentication required는 비밀번호가 설정되어 있음에도 AUTH 설정이 안되있어서 발생하는 에러이기도 하다. 이를 확인하려면 redis-cli에 접속해보자. 필자는 Docker container로 redis를 관리하고 있으므로 조금 다른 명령어를 사용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1727595716656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sckwon770@ubuntu:~/jnu-parking/storage$ sudo docker exec -it 77d53a9a5bb8 redis-cli
127.0.0.1:6379&amp;gt; ping
(error) NOAUTH Authentication required.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AUTH 설정이 문제라면 응답으로 pong이 와야하지만, 위와 같은 똑같은 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이상하다. 이때까지 별도로 레디스 설정을 관리하지 않아, 디폴트로 패스워드가 설정되지 않은채로 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;원인 파악&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 명이 개발하는 프로젝트이니, 누군가 설정 파일을 도입한걸까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml을 확인하니, 초기에 생성한 그대로이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1727596340743&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  redis:
    image: redis:alpine
    container_name: redis_container
    ports:
      - &quot;6379:6379&quot; # 로컬호스트에서 6380 포트로 Redis에 접근
    networks:
      - jnu-parking
    volumes:
      - redis_data:/data

volumes:
  redis_data:

networks:
  jnu-parking:
    external: true
~&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디폴트 패스워드가 별도로 존재했던걸까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에대한 공식 문서는 없었지만, docker-library-redis repo의 issues에서 redis-lab 개발자가 답변한 내용이 있다. 디폴트는 비밀번호가 없는 것이 맞다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-29 16.54.35.png&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y5X6l/btsJRn3UIyf/0cWmwE4VjexkDM3n44FkG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y5X6l/btsJRn3UIyf/0cWmwE4VjexkDM3n44FkG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y5X6l/btsJRn3UIyf/0cWmwE4VjexkDM3n44FkG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy5X6l%2FbtsJRn3UIyf%2F0cWmwE4VjexkDM3n44FkG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;999&quot; height=&quot;577&quot; data-filename=&quot;스크린샷 2024-09-29 16.54.35.png&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너 생성시에 별도로 설정파일을 매핑하지 않았으니, 별도로 생성되는 설정파일이 있거나 누군가 수정했던걸까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 컨테이너 내부에 redis.conf 파일 자체가 존재하지 않았다.. 기묘한 일이 아닐 수 없다...&lt;/p&gt;
&lt;pre id=&quot;code_1727596710763&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/data # cd /etc/redis
/bin/sh: cd: can't cd to /etc/redis: No such file or directory

/bin/sh: cd: can't cd to /usr/local/etc/redis: No such file or directory

/ # sudo find / -name &quot;redis.conf&quot;
/bin/sh: sudo: not found&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 알려진 내용대로 구축되어 있음에도 문제가 발생되고 있는 상태이다. 구축된 시스템들이 오픈소스이므로 결함이 있을수도 있고 자료가 outdated되어 변경사항이 있는 것일수도 있다. 다만, 하나 확실한 것은 시스템이 디폴트 값에 의존하고 있는 것은 올바른 내용이 아니다. 언제라도 발생할 문제였던 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 명시적으로 redis.conf를 매핑하고 비밀번호를 설정해 해결해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너의 redis.conf에 대해 매핑값을 추가하고&lt;/p&gt;
&lt;pre id=&quot;code_1727597091238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'

services:
  redis:
    image: redis:alpine
    container_name: redis_container
    ports:
      - &quot;6379:6379&quot; # 로컬호스트에서 6380 포트로 Redis에 접근
    networks:
      - jnu-parking
    volumes:
      - redis_data:/data
      - ./redis.conf:/etc/redis/redis.conf

volumes:
  redis_data:

networks:
  jnu-parking:
    external: true
~&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis.conf 내용을 작성하자. redis.conf 파일의 공식 예시는 (&lt;a href=&quot;https://redis.io/docs/latest/operate/oss_and_stack/management/config-file/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;) 에서 확인할 수 있다. 필자의 경우 잘 정리되어 있는 &lt;a href=&quot;https://ksh-coding.tistory.com/129#2-2.%20Redis%20Config%20%ED%99%98%EA%B2%BD%20%EC%84%A4%EC%A0%95%20%ED%8C%8C%EC%9D%BC%20%EC%83%9D%EC%84%B1-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포스팅의 설정파일&lt;/a&gt;을 참고했다.&lt;/p&gt;
&lt;pre id=&quot;code_1727597159043&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 연결 가능한 네트위크(0.0.0.0 = Anywhere)
bind 0.0.0.0

# 연결 포트
port 6379

# Master 노드의 기본 사용자 비밀번호
requirepass redis

# 최대 사용 메모리 용량(Default : 시스템 전체 용량)
maxmemory 2gb

# 설정된 최대 사용 메모리 용량을 초과했을때 처리 방식
# - noeviction : 쓰기 동작에 대해 error 반환 (Default)
# - volatile-lru : expire 가 설정된 key 들중에서 LRU algorithm 에 의해서 선택된 key 제거
# - allkeys-lru : 모든 key 들 중 LRU algorithm에 의해서 선택된 key 제거
# - volatile-random : expire 가 설정된 key 들 중 임의의 key 제거
# - allkeys-random : 모든 key 들 중 임의의 key 제거
# - volatile-ttl : expire time(TTL)이 가장 적게 남은 key 제거 (minor TTL)
maxmemory-policy volatile-ttl

# RDB 설정 (주기적 백업)
# 15분 안에 최소 1개 이상의 key가 변경되었을 때
save 900 1
# 5분 안에 최소 10개 이상의 key가 변경되었을 때
save 300 10
# 60초 안에 최소 10000개 이상의 key가 변경되었을 때
save 60 10000

# AOF 설정 (쓰기에 대한 로그 파일 저장)
## AOF 사용 여부
# appendonly yes
# 저장할 AOF 파일명
# appendfilename appendonly.aof
# 디스크와 동기화 처리 방식
# - always : AOF 값을 추가할 때마다 fsync를 호출해서 디스크에 쓰기
# - everysec : 매초마다 fsync를 호출해서 디스크에 쓰기
# - no : OS가 실제 sync를 할 때까지 따로 설정하지 않음
# appendfsync everysec


## Replication 관련 설정
## Slave Redis 설정
#slaveof 127.0.0.1 6380&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 컨테이너를 새로 띄우고 확인해보자. 정상적으로 PONG 응답이 온다.&lt;/p&gt;
&lt;pre id=&quot;code_1727597321509&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;127.0.0.1:6379&amp;gt; ping
PONG&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Spring 기동에 사용되는 .env 파일에도 redis_password 값을 넣고, 재기동하면 문제가 해결된다!&lt;/p&gt;</description>
      <category>   Backend/Database</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/72</guid>
      <comments>https://way-code.tistory.com/entry/Caused-by-orgredissonclientRedisAuthRequiredException-NOAUTH-Authentication-required-%EC%9B%90%EC%9D%B8-%EB%B0%8F-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95#entry72comment</comments>
      <pubDate>Sun, 29 Sep 2024 17:09:25 +0900</pubDate>
    </item>
    <item>
      <title>Nginx Proxy Manager로 서버 프록시 라우팅부터 정적 페이지 라우팅까지 간단하게 구축하기</title>
      <link>https://way-code.tistory.com/entry/Nginx-Proxy-Manager%EB%A1%9C-%EC%84%9C%EB%B2%84-%ED%94%84%EB%A1%9D%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85%EB%B6%80%ED%84%B0-%EC%A0%95%EC%A0%81-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85%EA%B9%8C%EC%A7%80-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx는 프론트부터 백엔드까지 프로젝트를 배포 및 운영하려면 꼭 고쳐야할 관문처럼 하나라고 생각한다. 하지만 처음 접하면 색깔도 없는 터미널에서 글자 하나만 틀려도 오류가 발생하고 원인을 찾는 것도 굉장히 어려워 골머리를 앓곤했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 쉬운 방법만이 능사는 아니지만, 기술적 역량이나 아키텍처에 공을 들여야하는 부분이 아니다보니 세세한 인프라 설정에 시간을 들이는 것 보다 어플리케이션 단에서 기술적 챌린지에 시간을 쏟는게 더 낫다고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx Proxy Manager 란&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 19.04.45.png&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJJs4A/btsJIUgjMVa/padxPgLOQK0Imgj6rkqrk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJJs4A/btsJIUgjMVa/padxPgLOQK0Imgj6rkqrk0/img.png&quot; data-alt=&quot;https://nginxproxymanager.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJJs4A/btsJIUgjMVa/padxPgLOQK0Imgj6rkqrk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJJs4A%2FbtsJIUgjMVa%2FpadxPgLOQK0Imgj6rkqrk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1139&quot; height=&quot;785&quot; data-filename=&quot;스크린샷 2024-09-21 19.04.45.png&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nginxproxymanager.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx Proxy Manager는 Nginx를 Web console기반으로 관리 가능한 GUI 오픈소스이다. 꽤 많은 유즈케이스를 커버 가능한데, 단순 서버 프록시 (라우팅)부터 정적 파일 호스팅까지 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &amp;nbsp;정적 파일 호스팅의 경우, repo issue에서 컨트리뷰터와 사용자들이 advanced기능을 이용해 커스텀 설정을 입력해 사용한 것이다. 따라서 nginx에 큰 변경이 있거나 Nginx Proxy Manager 새로운 버전에서 작동하지 않을 수 있으니, 기존에 구축된 시스템의 버전 업데이트를 유의하고 새로운 시스템을 구축하다가 동작하지 않으면 새로운 방법을 찾아야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Setup&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker Compose 이용한 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nginxproxymanager.com/guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 도큐먼트&lt;/a&gt;에서도 Docker Compose를 이용해 설치할도록 유도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://docs.docker.com/get-docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker&amp;nbsp;Install&amp;nbsp;documentation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://docs.docker.com/compose/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker-Compose&amp;nbsp;Install&amp;nbsp;documentation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. docker-compose.yml 작성&lt;/p&gt;
&lt;pre id=&quot;code_1726913999743&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
      - /opt/websites:/mnt/user/appdata/NginxProxyManager/websites&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;/opt/websites:/mnt/user/appdata/NginxProxyManager/websites&lt;/span&gt;&lt;/i&gt; : 정적 파일 호스팅을 위한 커스텀 설정이다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;/opt/websites&lt;/i&gt;&lt;/span&gt; : 호스팅할 정적 파일이 들어있는 경로이다. 내가 원하는대로 설정하면 된다. (필자의 경우, 리액트 프로젝트가 빌드된 것이 바로 반영되도록 하기 위해, &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;&lt;b&gt;..../Project-FE/dist/&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 으로 설정하였다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;/mnt/user/appdata/NginxProxyManager/websites&lt;/span&gt;&lt;/i&gt; : Nginx Proxy Manager의 경로이다. 수정 불가. 오타없도록 유의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Run Docker Compose&lt;/p&gt;
&lt;pre id=&quot;code_1726914400968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d

# If using docker-compose-plugin
docker compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Nginx Proxy Manager 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;81번 포트가 관리 콘솔이다. 초기 계정은 &lt;b&gt;admin@example.com / changeme&lt;/b&gt; 이다. 접속하면 바로 변경하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 19.29.32.png&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJhw4z/btsJHWzcw4i/2W8gMLvvDKoIK7Yj8nlk1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJhw4z/btsJHWzcw4i/2W8gMLvvDKoIK7Yj8nlk1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJhw4z/btsJHWzcw4i/2W8gMLvvDKoIK7Yj8nlk1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJhw4z%2FbtsJHWzcw4i%2F2W8gMLvvDKoIK7Yj8nlk1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1011&quot; height=&quot;859&quot; data-filename=&quot;스크린샷 2024-09-21 19.29.32.png&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공되는 주요 기능은 5가지 이다. 본 포스팅에서는 가장 대중적이고 직접 사용해본 1번 5번만 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Proxy Hosts : 서버로 들어온 요청을 특정 위치로 라우팅한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Redirection Hosts : 특정 도메인으로 접속된 요청을 다른 도메인으로 리다이렉션 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Streams : Nginx의 새로운 기능인 Stream은 TCP/UDP 트래픽을 네트워크상의 다른 컴퓨터로 직접 전달해준다. 게임 서버나 FTP, SSH 서버 운영에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 404 Hosts : 404 응답 페이지 설정을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. SSL Certificates : LetsEncrypt 기반 SSL 인증 설정을 관리한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 19.34.49.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RlWiD/btsJGAjNrYZ/VI6jdF32M7F9X9VXcVzSZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RlWiD/btsJGAjNrYZ/VI6jdF32M7F9X9VXcVzSZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RlWiD/btsJGAjNrYZ/VI6jdF32M7F9X9VXcVzSZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRlWiD%2FbtsJGAjNrYZ%2FVI6jdF32M7F9X9VXcVzSZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;994&quot; height=&quot;308&quot; data-filename=&quot;스크린샷 2024-09-21 19.34.49.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. SSL Certificates&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 19.59.19.png&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beKJfn/btsJG3TmqrM/PVhQUYcIBKEPQrKFZkJ0ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beKJfn/btsJG3TmqrM/PVhQUYcIBKEPQrKFZkJ0ak/img.png&quot; data-alt=&quot;필자는 이미 등록한 SSL이 있으니 무시하고 보면 된다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beKJfn/btsJG3TmqrM/PVhQUYcIBKEPQrKFZkJ0ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeKJfn%2FbtsJG3TmqrM%2FPVhQUYcIBKEPQrKFZkJ0ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;536&quot; data-filename=&quot;스크린샷 2024-09-21 19.59.19.png&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;필자는 이미 등록한 SSL이 있으니 무시하고 보면 된다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;letsencrypt를 기반으로 소유한 도메인에 대한 SSL 설정을 관리한다. Proxy Hosts에서 https SSL 접속을 지원하려면 가장 먼저 필수적으로 설정해야 한다. 도메인을 설정하는 방법은 2가지가 있는데, 사용할 도메인을 전부 정의하는 방법과 DNS Challenge 설정을 통해 와일드카드 설정을 하는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 19.52.05.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uUpK7/btsJIootlzO/nLlzdWVS1ZWtmTrcujn7ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uUpK7/btsJIootlzO/nLlzdWVS1ZWtmTrcujn7ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uUpK7/btsJIootlzO/nLlzdWVS1ZWtmTrcujn7ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuUpK7%2FbtsJIootlzO%2FnLlzdWVS1ZWtmTrcujn7ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;547&quot; data-filename=&quot;스크린샷 2024-09-21 19.52.05.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용할 도메인 명시적 정의하는 법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx로 들어온 요청을 라우팅하는 것이므로, 우선 사용할 도메인을 정의하고 서버 IP addr로 라우팅을 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.03.07.png&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kZBgV/btsJItJ29Ml/fPl267hnccLMRc182lNtak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kZBgV/btsJItJ29Ml/fPl267hnccLMRc182lNtak/img.png&quot; data-alt=&quot;사진은 Route53이지만, CloudFlare 등 다른 DNS 환경도 마찬가지이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kZBgV/btsJItJ29Ml/fPl267hnccLMRc182lNtak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkZBgV%2FbtsJItJ29Ml%2FfPl267hnccLMRc182lNtak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;818&quot; data-filename=&quot;스크린샷 2024-09-21 20.03.07.png&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진은 Route53이지만, CloudFlare 등 다른 DNS 환경도 마찬가지이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의한 도메인을 모두 적고, 'Test Server Reachability'를 눌러 유효성을 검증한다. 검증이 완료하면 바로 'Save'로 생성하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.05.55.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPavf6/btsJIVzxDR6/sWZY0DDGhSM4KlVc9viQI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPavf6/btsJIVzxDR6/sWZY0DDGhSM4KlVc9viQI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPavf6/btsJIVzxDR6/sWZY0DDGhSM4KlVc9viQI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPavf6%2FbtsJIVzxDR6%2FsWZY0DDGhSM4KlVc9viQI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;547&quot; data-filename=&quot;스크린샷 2024-09-21 20.05.55.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. DNS Challenge, 와일드카드(에스터리스크) 사용하는 법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 도메인의 소유권을 확인시켜줌으로써, 도메인을 일일이 정의하지 않아도 된다. 다만, 더 자세한 인증정보를 요구하는데, Route53의 경우 IAM의 accessKey, secretKey를 요구하니, 각자의 판단을 따르면 될 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.09.01.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVVE9k/btsJIRxaacs/hli2swkl7C3Bc5WyNgR3z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVVE9k/btsJIRxaacs/hli2swkl7C3Bc5WyNgR3z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVVE9k/btsJIRxaacs/hli2swkl7C3Bc5WyNgR3z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVVE9k%2FbtsJIRxaacs%2Fhli2swkl7C3Bc5WyNgR3z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;910&quot; data-filename=&quot;스크린샷 2024-09-21 20.09.01.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1. Proxy Hosts&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.13.18.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvA4S5/btsJGIWpwA5/7JYeHthdsJviADSOnCS3a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvA4S5/btsJGIWpwA5/7JYeHthdsJviADSOnCS3a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvA4S5/btsJGIWpwA5/7JYeHthdsJviADSOnCS3a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvA4S5%2FbtsJGIWpwA5%2F7JYeHthdsJviADSOnCS3a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;995&quot; height=&quot;454&quot; data-filename=&quot;스크린샷 2024-09-21 20.13.18.png&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 사용하는 기능으로 특정 도메인을 통한 접속을 서버 내 리소스로 라우팅할 수 있다. 두 가지 활용법이 있는데, 1. 포트를 기준으로 리소스로 라우팅과 2. 정적 파일 호스팅이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-1. 포트 기준 리소스 라우팅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;81번 포트로 접속하고 있는 nginx proxy manager 콘솔을 기준으로 알아보자. &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;nginx.test.com&lt;/i&gt;&lt;/span&gt; 도메인을 서버를 향해 라우팅해 정의되었다면, &lt;b&gt;'Domain Names'&lt;/b&gt;에 정의된(사용할) 도메인을 적는다. SSL 인증서를 등록했다면 &lt;b&gt;'Scheme'&lt;/b&gt;를 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;https&lt;/i&gt;&lt;/span&gt;로 변경하고, &lt;b&gt;'Forward Hostname / IP'&lt;/b&gt; 를 현재 서버 아아피로 작성하고 &lt;b&gt;'Forward Port'&lt;/b&gt;는 Nginx proxy Manager web console이 LISTEN하고 있는 81번 포르를 적는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.14.36.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cph4T5/btsJIP7buNz/iFKrjtNOy3hTcOJVZ3XGY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cph4T5/btsJIP7buNz/iFKrjtNOy3hTcOJVZ3XGY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cph4T5/btsJIP7buNz/iFKrjtNOy3hTcOJVZ3XGY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcph4T5%2FbtsJIP7buNz%2FiFKrjtNOy3hTcOJVZ3XGY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;569&quot; data-filename=&quot;스크린샷 2024-09-21 20.14.36.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요에 따라 SSL 탭에서 인증서를 설정하고 &lt;b&gt;'Save'&lt;/b&gt;하여 프록시 설정을 생성하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.18.16.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4dAvp/btsJIpgAI5J/ukLRWBBNfjAVRlTipoKf70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4dAvp/btsJIpgAI5J/ukLRWBBNfjAVRlTipoKf70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4dAvp/btsJIpgAI5J/ukLRWBBNfjAVRlTipoKf70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4dAvp%2FbtsJIpgAI5J%2FukLRWBBNfjAVRlTipoKf70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;409&quot; data-filename=&quot;스크린샷 2024-09-21 20.18.16.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx처럼 따로 service restart할 필요 없이, 짧은 대기시간 후 바로 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2. 정적 파일 호스팅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 프론트 팀원들이 개발한 리액트 프로젝트를 개발 서버에 띄우려고 하는데, 2개의 모듈로 분리되어 있는 구조이다보니 기존에 쓰던 PM2 배포 방식을 사용하기 곤란했다. pnpm을 이용해 별도로 빌드하고, 빌드 폴더 안에 있는 각 모듈의 index.html만 라우팅하면 된다고 해서 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NginxProxyManager/nginx-proxy-manager 레퍼의 이슈 타레(&lt;a href=&quot;https://github.com/NginxProxyManager/nginx-proxy-manager/issues/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Use&amp;nbsp;nginx-proxy-manager&amp;nbsp;as&amp;nbsp;web&amp;nbsp;server&amp;nbsp;#280&lt;/a&gt;)를 보고 구현했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.24.11.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dv6TNW/btsJGMEll1f/A4maWnlKpKrXhepfJ2vIF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dv6TNW/btsJGMEll1f/A4maWnlKpKrXhepfJ2vIF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dv6TNW/btsJGMEll1f/A4maWnlKpKrXhepfJ2vIF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdv6TNW%2FbtsJGMEll1f%2FA4maWnlKpKrXhepfJ2vIF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;937&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2024-09-21 20.24.11.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml을 작성할 때, &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;/opt/websites&lt;/i&gt;&lt;/span&gt; 에 대한 설명을 기억하는가? 그 부분이 이 부분이다. NginxProxyManager 내부의 웹 호스팅 폴더와 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;/opt/websites&lt;/i&gt;&lt;/span&gt;를 마운트하게 된다. 본 nginx의 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;../site_enbles/..&lt;/i&gt;&lt;/span&gt;, &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;../sites-available/..&lt;/i&gt;&lt;span style=&quot;color: #000000;&quot;&gt;와 동일하다고 보면 될 것 같다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필자의 경우, 테스트 서버 내의 리액트 프로젝트 구조가 아래와 같은데, 빌드시 dist 폴더 안에 모듈별로 생성되므로 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;/opt/websites/&lt;/i&gt;&lt;/span&gt;를 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;/home/sckwon/project/FE/dist&lt;/i&gt;&lt;/span&gt;로 변경하였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.34.15.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8geK7/btsJHk1SRUC/BtJvjtDpn1vpjCIK98OxnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8geK7/btsJHk1SRUC/BtJvjtDpn1vpjCIK98OxnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8geK7/btsJHk1SRUC/BtJvjtDpn1vpjCIK98OxnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8geK7%2FbtsJHk1SRUC%2FBtJvjtDpn1vpjCIK98OxnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;464&quot; data-filename=&quot;스크린샷 2024-09-21 20.34.15.png&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모듈에 연결할 도메인을 설정하고, &lt;b&gt;'Forward Hostname / IP'&lt;/b&gt;와&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;'Forward Port' &lt;/b&gt;아무 값이나 적으면 된다. 파일에 라우팅할 것이지만, 필수 값으로 UI가 설정되어 있으므로 적어야 하기 때문이다. SSL도 마찬가지로 설정해준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.22.49.png&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB0IU0/btsJHmk2QN2/3ClPyH8wz8Vc5M2T6YTyrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB0IU0/btsJHmk2QN2/3ClPyH8wz8Vc5M2T6YTyrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB0IU0/btsJHmk2QN2/3ClPyH8wz8Vc5M2T6YTyrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB0IU0%2FbtsJHmk2QN2%2F3ClPyH8wz8Vc5M2T6YTyrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;574&quot; data-filename=&quot;스크린샷 2024-09-21 20.22.49.png&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Cache Assets을 사용한 경우, js와 css가 제대로 로드되지 않는 이슈가 있다는 코멘트가 있으니 테스트해보고 싶은 것이 아니라면 섣불리 사용하진 말자.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.36.42.png&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTCEvS/btsJHPAfscy/RoQXzpuFcP8JzJDiM6kVN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTCEvS/btsJHPAfscy/RoQXzpuFcP8JzJDiM6kVN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTCEvS/btsJHPAfscy/RoQXzpuFcP8JzJDiM6kVN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTCEvS%2FbtsJHPAfscy%2FRoQXzpuFcP8JzJDiM6kVN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2024-09-21 20.36.42.png&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;'Advanced'&lt;/b&gt; 탭에서 index.html라우팅을 설정하면 된다. 헷갈리면 안되는 부분이 Nginx proxy manager는 도커 컨테이너에 떠있으므로 서버 루트 경로를 기준으로 생각하면 안된다. docker compose에서 설정한대로, 마운팅햇떤 NginxProxyManager 내부 경로를 기준으로 적으면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-21 20.39.04.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSmtR1/btsJGW1fkpn/qbOsGDO3yIZtPl4zPBKIGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSmtR1/btsJGW1fkpn/qbOsGDO3yIZtPl4zPBKIGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSmtR1/btsJGW1fkpn/qbOsGDO3yIZtPl4zPBKIGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSmtR1%2FbtsJGW1fkpn%2FqbOsGDO3yIZtPl4zPBKIGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;631&quot; data-filename=&quot;스크린샷 2024-09-21 20.39.04.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 websites와 마운팅한 dist폴더 안에 모듈별로 빌드한 프로젝트가 생성되므로, root 경로에 해당 빌드 폴더의 이름을 적으면 된다. 그리고 &lt;b&gt;'Save'&lt;/b&gt;를 눌러 생성하면 끝이다!&lt;/p&gt;
&lt;pre id=&quot;code_1757575406238&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root /mnt/user/appdata/NginxProxyManager/websites/service-home;
index index.html;
location / {
    try_files $uri /index.html /index.html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 우리 프로젝트처럼 별도의 모듈 없이 dist폴더 안에 바로 index.html이 있다면, 아래와 같이 root 경로를 설정하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1757575404099&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;root /mnt/user/appdata/NginxProxyManager/websites;
index index.html;
location / {
    try_files $uri /index.html /index.html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 파일 호스팅하느라 삽질한 저의 포스팅으로 다른 분들은 삽질하지 않길...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx Proxy Manager로 평안한 배포 되길...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝.&lt;/p&gt;</description>
      <category>   Backend/Web</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/71</guid>
      <comments>https://way-code.tistory.com/entry/Nginx-Proxy-Manager%EB%A1%9C-%EC%84%9C%EB%B2%84-%ED%94%84%EB%A1%9D%EC%8B%9C-%EB%9D%BC%EC%9A%B0%ED%8C%85%EB%B6%80%ED%84%B0-%EC%A0%95%EC%A0%81-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85%EA%B9%8C%EC%A7%80-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0#entry71comment</comments>
      <pubDate>Sat, 21 Sep 2024 20:43:50 +0900</pubDate>
    </item>
    <item>
      <title>개발자들이 안심할 수 있는 삐삐 서비스 만들기&amp;nbsp;2탄 (그라파나 경보, Sentry)</title>
      <link>https://way-code.tistory.com/entry/BE-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EC%95%88%EC%8B%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%82%90%EC%82%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0-2%ED%83%84-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98-%EA%B2%BD%EB%B3%B4-Sentry</link>
      <description>&lt;h1&gt;서론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 운영을 해나가면서 기존에 수작업으로 서버 health check하는 방식으로는 서비스를 모니터링하기 어려워 프로메테우스를 도입해 서버 매트릭을 수집하고 그라파나를 커스터마이징해 삐삐 서비스를 위한 대시보드를 구축했습니다. 이를 통해 모니터링을 손쉽게할 수 있었지만, 또 다른 문제점이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삐삐는 24시간 라이브 서비스 중이지만 백엔드 개발자가 24시간 대시보드를 볼 수 없는 노릇이었습니다. 또, 프론트(iOS와 AOS)의 통합 테스트는 함께 진행하였지만, 모바일 플랫폼의 특성 상 심사를 거쳐 배포를 해야므로 배포할 때마다 대기할 수도 없었습니다. 그 외에도 API와 클라이언트 간의 버저닝이라던가 슬랙에 쌓이는 에러 로그 관리, 모바일 플랫폼 마다 따로 대응해줘야 하는 트러블슈팅 등 고려할 것이 산더미 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결해야 할 문제는 많아졌지만, 처음 경험해보는 서비스 운영의 영역에 도달했고 유의미한 트래픽이 발생하고 있다는 점에 너무 즐거웠습니다. 매일 출근할때마다 그라파나 대시보드만 바라봤던 것 같네요. 유저들이 이슈를 직접 발견하기 전에 개발자들이 파악하고 고칠 수 있는 시스템만들어 문제를 하나하나 해결해 보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;장애 경보, Issue Alert&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템을 도입하고나서 종종 비정상적인 트래픽이 관측되었습니다. 주로 토큰과 관련된 API에서 4XX 에러가 치솟았는데, iOS 팀에 리포팅하고 함께 원인을 파악해보니 토큰 갱신과 관련된 코드에서 무한 루프가 도는 버그가 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;11116&quot; data-origin-height=&quot;2341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFC38t/btsIurfyQXN/4tNe95lXUpYY5ivHSjUAYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFC38t/btsIurfyQXN/4tNe95lXUpYY5ivHSjUAYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFC38t/btsIurfyQXN/4tNe95lXUpYY5ivHSjUAYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFC38t%2FbtsIurfyQXN%2F4tNe95lXUpYY5ivHSjUAYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;11116&quot; height=&quot;2341&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;11116&quot; data-origin-height=&quot;2341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 초기부터 존재했던 버그였지만, 무한루프 돌던 토큰 갱신 코드와 별개로 신규 발급은 정상적으로 되었기 때문에 통합 테스트에서도 파악하지 못했던 것이였습니다. &lt;s&gt;(이대로 모르고 지나갔다면, 늘어난 유저만큼 서버가 DDOS를 당했&amp;hellip;)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 삐삐 2차 MVP를 배포할 때, 장애가 발생한 적이 있었습니다. 위의 토큰 버그를 고친 버전이 배포되었는데, 신규 회원 온보딩이 끝나는 조건과 변경된 토큰 코드가 절묘하게 엇나가면서 기존 회원들이 온보딩화면에 갖혀버리는 심각한 장애였습니다&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.jpg&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;3548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceVEmy/btsIupovG1M/kWEneSLczAr3RbIjTpf5K1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceVEmy/btsIupovG1M/kWEneSLczAr3RbIjTpf5K1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceVEmy/btsIupovG1M/kWEneSLczAr3RbIjTpf5K1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceVEmy%2FbtsIupovG1M%2FkWEneSLczAr3RbIjTpf5K1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;3548&quot; data-filename=&quot;2.jpg&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;3548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 회사 외부 교육 중이였는데, 팀 단톡방에서 친구들이 앱이 갑자기 동작안하다는 리포팅을 듣고 급하게 화장실로 가서 휴대폰으로 그라파나 확인하고 iOS 팀장님과 상의하고 깃허브 모바일로 핫픽스 이슈날린 기억이 있네요&amp;hellip;  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위의 두 케이스 모두 결과적으로는 도입한 모니터링 시스템을 통해 해결하였지만, 비정상 트래픽에 대한 경보가 없어 장애 감지에 활용하지 못하고 사후 조치한 케이스였습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grafana Alert&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그라파나에서는 다양한 경보 기능을 제공합니다. 비정상 트래픽에 대한 기준을 설정한다면, 정해진 인원들에게 알림을 전송하는 기능입니다. 이를 통해 빠르게 장애를 파악하는데 도움을 줄 수 있습니다. 가장 많이 활용하는 Slack에 적용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmbWie/btsIthEZazq/hplN8gEOr4KhpS2CfJe6P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmbWie/btsIthEZazq/hplN8gEOr4KhpS2CfJe6P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmbWie/btsIthEZazq/hplN8gEOr4KhpS2CfJe6P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmbWie%2FbtsIthEZazq%2FhplN8gEOr4KhpS2CfJe6P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;984&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Slack App 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 그라파나에 의해 트리거되어 슬랙 채널에 알림을 전송할 Slack App을 생성해야 합니다. &lt;a href=&quot;https://api.slack.com/apps&quot;&gt;Slack API 홈페이지&lt;/a&gt;에 접속합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMkrUY/btsIsfBltjp/A1EDUPryaRxlcVjo9zNPmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMkrUY/btsIsfBltjp/A1EDUPryaRxlcVjo9zNPmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMkrUY/btsIsfBltjp/A1EDUPryaRxlcVjo9zNPmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMkrUY%2FbtsIsfBltjp%2FA1EDUPryaRxlcVjo9zNPmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1067&quot; height=&quot;399&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. From scratch를 선택 후 이름과 알림을 전송할 workspace를 선택합니다. &lt;b&gt;( &amp;nbsp;App Name에 이모지 넣으면 에러가 발생하며 생성되지 않습니다!)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-10 00.23.51.png&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKZHr/btsItoD1R6t/O3lQQ48nc0CyPVpdbCHavk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKZHr/btsItoD1R6t/O3lQQ48nc0CyPVpdbCHavk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qKZHr/btsItoD1R6t/O3lQQ48nc0CyPVpdbCHavk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKZHr%2FbtsItoD1R6t%2FO3lQQ48nc0CyPVpdbCHavk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1785&quot; height=&quot;691&quot; data-filename=&quot;스크린샷 2024-07-10 00.23.51.png&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. App이 생성되면, 좌측 메뉴를 통해 [Features &amp;gt; App Home &amp;gt; Your App&amp;rsquo;s Presence in Slack]에서 Display Name과 Default Name을 설정해 봇 사용자를 생성합니다. &lt;b&gt;( &amp;nbsp;Display Name에 이모지 넣으면 에러가 발생하며 생성되지 않습니다!) ( &amp;nbsp;이 단계를 뛰어넘으면 이후 단계에서 에러가 발생합니다!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 슬랙은 Webhooks을 지원하기 때문에 Webhooks URL만 발급하면 간단히 알림을 전송할 수 있습니다.[Features &amp;gt; Incoming Webhooks &amp;gt; Add New Webhook to Workspace]를 통해 알림을 전송할 채널 전용 웹훅이 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Slack으로 돌아와 선택한 워크스페이스의 채널에서 &lt;b&gt;@APP-NAME&lt;/b&gt; (저의 경우 &lt;b&gt;@그라파나 경보&lt;/b&gt; ) 메시지를 입력하면, 생성한 봇을 해당 채널로 초대할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Zn56/btsIt5X68hZ/hqAFx3yGRyQkB0MpLIys40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Zn56/btsIt5X68hZ/hqAFx3yGRyQkB0MpLIys40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Zn56/btsIt5X68hZ/hqAFx3yGRyQkB0MpLIys40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Zn56%2FbtsIt5X68hZ%2FhqAFx3yGRyQkB0MpLIys40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1492&quot; height=&quot;694&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  그라파나 경보 앱에 설치할 봇 사용자가 없습니다. 에러가 발생한다면, [App Home &amp;gt; Display Name] 단계를 건너뛰었기 때문입니다. Display Name과 Default Name을 설정함으로써, Slack App의 Bot profile 생성이 완료됩니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdPtpw/btsIurmkyHM/s6YdFXHe6iBH6U164nyEP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdPtpw/btsIurmkyHM/s6YdFXHe6iBH6U164nyEP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdPtpw/btsIurmkyHM/s6YdFXHe6iBH6U164nyEP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdPtpw%2FbtsIurmkyHM%2Fs6YdFXHe6iBH6U164nyEP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;304&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그라파나 Alert Rule 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비는 완료되었으니 그라파나에서 경보를 설정해봅시다. Alert Rule은 그라파나가 언제 경보를 발생시킬지에 대한 규칙을 설정하는 것입니다. 경보 조건은 3가지로 구성되는데&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Query : 대시보드를 생성할 때와 마찬가지로 PromQL을 통해 조건의 대상이 될 매트릭을 조회하기 위한 쿼리를 작성합니다.&lt;/li&gt;
&lt;li&gt;Reduce : 조회한 매트릭을 Last, Min, Max 등의 연산을 통해 한 번 필터링합니다.&lt;/li&gt;
&lt;li&gt;Threshold : 얻어진 값을 기준으로 경보를 발생항 정확한 조건을 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brXNGu/btsIt7uMZrc/bYZTRZV03lNWYEDLGl1iXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brXNGu/btsIt7uMZrc/bYZTRZV03lNWYEDLGl1iXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brXNGu/btsIt7uMZrc/bYZTRZV03lNWYEDLGl1iXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrXNGu%2FbtsIt7uMZrc%2FbYZTRZV03lNWYEDLGl1iXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1215&quot; height=&quot;760&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 10분간 서버 응답코드가 4XX인 수가 100을 넘어가면 트리거되도록 설정했습니다. 저희 서버는 토큰 유효성 검증과 만료를 4XX 코드로 핸들링함으로 임계값을 조금 높게 잡았는데, 각자 서버나 클라이언트 환경에 맞춰 설정하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGHtXo/btsIt72BKWa/HZqfJAIruQMu29XlNqf981/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGHtXo/btsIt72BKWa/HZqfJAIruQMu29XlNqf981/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGHtXo/btsIt72BKWa/HZqfJAIruQMu29XlNqf981/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGHtXo%2FbtsIt72BKWa%2FHZqfJAIruQMu29XlNqf981%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;924&quot; data-filename=&quot;8.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그라파나 Contact point 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Contact point는 경보를 보낼 대상을 설정하는 것입니다. 아까 생성한 Webhook URL을 입력하고 테스트해보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/neWrQ/btsIsBYrYdg/olPocGjL2JEy142LOxLTjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/neWrQ/btsIsBYrYdg/olPocGjL2JEy142LOxLTjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/neWrQ/btsIsBYrYdg/olPocGjL2JEy142LOxLTjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FneWrQ%2FbtsIsBYrYdg%2FolPocGjL2JEy142LOxLTjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1107&quot; height=&quot;607&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 작동하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zQkG4/btsItkuVXlb/DVkHkGlQwZ220IvYTKWSrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zQkG4/btsItkuVXlb/DVkHkGlQwZ220IvYTKWSrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zQkG4/btsItkuVXlb/DVkHkGlQwZ220IvYTKWSrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzQkG4%2FbtsItkuVXlb%2FDVkHkGlQwZ220IvYTKWSrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;494&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그라파나 Notification policies 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룰과 대상을 정했으면, 두 개를 서로 연결해야 의도한대로 경보가 발송될 수 있습니다. Default policy를 Edit하고 생성한 contact point와 grafana_folder를 설정해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b42rQv/btsIsLs0Mx5/xP3qfyz4A1nRm4M6hh2rZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b42rQv/btsIsLs0Mx5/xP3qfyz4A1nRm4M6hh2rZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b42rQv/btsIsLs0Mx5/xP3qfyz4A1nRm4M6hh2rZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb42rQv%2FbtsIsLs0Mx5%2FxP3qfyz4A1nRm4M6hh2rZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;368&quot; data-filename=&quot;12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;에러 추적, Error Tracking&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애가 발생하면 빠르게 파악할 수 있게 되었지만, 보고받은 장애의 원인을 분석하는데 어려움이 있었습니다. 현재는 서버에서 에러가 발생하면 error message와 payload, stack trace를 슬랙에 전송하여 메시지를 기반으로 문제를 재현하고 디버깅하기 시작하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;2060&quot; data-origin-height=&quot;2062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UjGNS/btsIuakKEdG/NATnbG1skUzUsUl2idIjI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UjGNS/btsIuakKEdG/NATnbG1skUzUsUl2idIjI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UjGNS/btsIuakKEdG/NATnbG1skUzUsUl2idIjI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUjGNS%2FbtsIuakKEdG%2FNATnbG1skUzUsUl2idIjI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2060&quot; height=&quot;2062&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;2060&quot; data-origin-height=&quot;2062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 초반에는 무척 편리하였지만, 서비스 운영과 동시에 개발을 진행하면서 원하는 에러 로그를 찾는 것이 무척 어려웠습니다. 아래와 같이 &lt;span style=&quot;background-color: #000000; color: #eb5757;&quot; data-token-index=&quot;1&quot;&gt;POST /v1/posts&lt;/span&gt; API에 대한 로그를 검색하였지만 전혀 다른 로그만 검색되는 일이 빈번했는데, 단순히 메시지의 문자열 검색에만 최적화된 슬랙을 로그 관리 용으로 사용하기 적합하지 않다고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sentry&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data dogs, AWS CloudWatch 등 다양한 로그 관리 솔루션이 있었지만, 저희는 Sentry에 주목하였는데 그 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 로그 관리 솔루션이 아니지만, 로그 이상으로 디버깅하는데 필요한 굉장히 많은 정보를 제공합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사진은 Breadscrumbs 기능인데 웹 사이트에서 동작한 모든 행동을 캡쳐하여 기록합니다. Breadscrumbs를 포함하여 에러가 발생한 API를 호출한 클라이언트는 어느 플랫폼이고 어떤 데이터를 전송했는지 등을 별도의 설정없이 자동으로 기록됩니다. 만약, 로깅 코드를 대신해 Sentry error capture 코드를 추가 작성한다면 굉장히 자세한 정보를 확인할 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Atj1t/btsIsfnM521/9vHGnf7t5MqTVATZzkGzKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Atj1t/btsIsfnM521/9vHGnf7t5MqTVATZzkGzKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Atj1t/btsIsfnM521/9vHGnf7t5MqTVATZzkGzKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAtj1t%2FbtsIsfnM521%2F9vHGnf7t5MqTVATZzkGzKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;868&quot; height=&quot;515&quot; data-filename=&quot;15.png&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 프론트엔드 에러 트래킹 솔루션으로 더 유명한 Sentry이지만, Spring boot에 대한 지원과 기능이 매우 다양했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 대표적으로는 Performance Monitoring 기능인데, WAS 서버에서 Mapping 중인 API 별로 응답속도, 실패율, 호출한 유니크 유저 수, 사용 빈도 등 서버 성능 최적화에 필요한 다양한 지표를 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;15.webp&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFwdPd/btsIsyACREX/CTaZEusDR9w7dATsd2akP0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFwdPd/btsIsyACREX/CTaZEusDR9w7dATsd2akP0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFwdPd/btsIsyACREX/CTaZEusDR9w7dATsd2akP0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFwdPd%2FbtsIsyACREX%2FCTaZEusDR9w7dATsd2akP0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;1268&quot; data-filename=&quot;15.webp&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 프론트엔드, 백엔드 모두에게 필요한 에러 추적 솔루션입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 보다 프론트엔트 솔루션으로 더 유명한 만큼 iOS, AOS 플랫폼에 적용하여 서비스 전체의 에러 추적이 가능했습니다. Sentry 시스템 하나로 여러 프로젝트 추적이 가능하므로 플랫폼마다 제공된 API Docs를 통해 손쉽게 적용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 단점도 존재했지만 단 한가지입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 비쌉니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sentry는 Saas 형태로 제공되어 프로젝트와 연결만하면 되는 Cloud Sentry와 직접 서버에 설치하고 연결하는 On-premise Sentry로 나뉩니다. Cloud Sentry의 경우 Team 요금즈는 매달 35000원 정도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pcUyV/btsIseoVF3V/Sx08HDAQtfR6m5oz7Be8gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pcUyV/btsIseoVF3V/Sx08HDAQtfR6m5oz7Be8gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pcUyV/btsIseoVF3V/Sx08HDAQtfR6m5oz7Be8gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpcUyV%2FbtsIseoVF3V%2FSx08HDAQtfR6m5oz7Be8gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;749&quot; data-filename=&quot;16.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;on-premise sentry는 최소 AWS xlarge ec2가 필요하므로 매달 16만원정도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;3242&quot; data-origin-height=&quot;2046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z302q/btsIt45XS1E/g1iDW1TFoYhoPaqUUkB0i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z302q/btsIt45XS1E/g1iDW1TFoYhoPaqUUkB0i1/img.png&quot; data-alt=&quot;Sentry Docker Service 설치시의 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z302q/btsIt45XS1E/g1iDW1TFoYhoPaqUUkB0i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ302q%2FbtsIt45XS1E%2Fg1iDW1TFoYhoPaqUUkB0i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3242&quot; height=&quot;2046&quot; data-filename=&quot;17.png&quot; data-origin-width=&quot;3242&quot; data-origin-height=&quot;2046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sentry Docker Service 설치시의 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 서비스 모두 부담되는 가격이지만, 프로젝트 팀원이 개인적으로 소유한 AWS Credit을 활용할 수 있었기 때문에 AWS EC2에 온프레미스 형태로 설치하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker container for Sentry&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온프레미스 설치의 경우, Self-Hosted Sentry용 도커 설치 파일과 가이드(&lt;a href=&quot;https://develop.sentry.dev/self-hosted/#getting-started&quot;&gt;https://develop.sentry.dev/self-hosted/#getting-started&lt;/a&gt;)를 제공하고 있습니다. Sentry를 호스팅할 서버의 터미널를 통해 실행하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# Assuming current latest version is 24.1.0
# Current actual version can be acquired from the Releases page on GitHub
VERSION=&quot;24.1.0&quot;
git clone &amp;lt;https://github.com/getsentry/self-hosted.git&amp;gt;
cd self-hosted
git checkout ${VERSION}
sudo ./install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo docker ps 를 통해 실행 중인 sentry container들이 확인되고, server_url:9000 에 접속시 Sentry 콘솔 로그인 창이 뜬다면 정상 설치된 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;21.png&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VJzSf/btsIuqnpuEU/Tn50LO341YMVh5Zsksspb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VJzSf/btsIuqnpuEU/Tn50LO341YMVh5Zsksspb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VJzSf/btsIuqnpuEU/Tn50LO341YMVh5Zsksspb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVJzSf%2FbtsIuqnpuEU%2FTn50LO341YMVh5Zsksspb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;537&quot; data-filename=&quot;21.png&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  반드시 4 vCPU + 16GB RAM 이상 인스턴스에 설치하세요. (Sentry container 설치 중 계속 터미널이 멈추는 경우) &lt;br /&gt;&lt;span style=&quot;background-color: #ef5369; color: #ffffff;&quot;&gt;CPU와 RAM 하나라도 모자랄 경우, 컨테이너 설치 중 성능 부족으로 인해 인스턴스가 멈춰버립니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWKInK/btsIscLqkax/iIZE5K96DpTktM2PR6L0B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWKInK/btsIscLqkax/iIZE5K96DpTktM2PR6L0B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWKInK/btsIscLqkax/iIZE5K96DpTktM2PR6L0B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWKInK%2FbtsIscLqkax%2FiIZE5K96DpTktM2PR6L0B1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;184&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;23.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXKg3p/btsIseWKX7q/KXK2o2XjD6iT85SFR6J0LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXKg3p/btsIseWKX7q/KXK2o2XjD6iT85SFR6J0LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXKg3p/btsIseWKX7q/KXK2o2XjD6iT85SFR6J0LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXKg3p%2FbtsIseWKX7q%2FKXK2o2XjD6iT85SFR6J0LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;188&quot; data-filename=&quot;23.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sentry Project 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Sentry와 스프링 부트를 연결해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sentry 콘솔에 접속해 Spring boot 프로젝트를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;24.png&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6BAnV/btsItEmna8T/fykWqAAk1xo53TWTUFaDe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6BAnV/btsItEmna8T/fykWqAAk1xo53TWTUFaDe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6BAnV/btsItEmna8T/fykWqAAk1xo53TWTUFaDe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6BAnV%2FbtsItEmna8T%2FfykWqAAk1xo53TWTUFaDe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1139&quot; height=&quot;298&quot; data-filename=&quot;24.png&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;25.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XW9AW/btsIsc5K0NQ/02M9Mg15u4wBgUkzjQj3p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XW9AW/btsIsc5K0NQ/02M9Mg15u4wBgUkzjQj3p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XW9AW/btsIsc5K0NQ/02M9Mg15u4wBgUkzjQj3p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXW9AW%2FbtsIsc5K0NQ%2F02M9Mg15u4wBgUkzjQj3p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;895&quot; height=&quot;1167&quot; data-filename=&quot;25.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;1167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 생성하면, 연결을 위한 가이드를 제공하는데 가이드를 그대로 따라하기를 권장합니다. 별도로 스프링 부트 도큐먼트(&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/java/guides/spring-boot/&quot;&gt;https://docs.sentry.io/platforms/java/guides/spring-boot/&lt;/a&gt;)를 공식적으로 제공하지만, Spring boot 3 기준 콘솔 내부에서 제공하는 가이드가 오류 없이 정상 작동하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;31.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mpPdf/btsIsevJlm3/tYZkZLwylNcfUlfT6KyBnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mpPdf/btsIsevJlm3/tYZkZLwylNcfUlfT6KyBnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mpPdf/btsIsevJlm3/tYZkZLwylNcfUlfT6KyBnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmpPdf%2FbtsIsevJlm3%2FtYZkZLwylNcfUlfT6KyBnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;1155&quot; data-filename=&quot;31.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sentry Issue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 연결되면 WAS에서 예외가 발생할때마다 Issues 탭에 다음과 같이 기록됩니다. 클릭 시 상세한 에러 트레이싱 정보를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;32.png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwwGrq/btsIs2VCSxm/LbFNulV61e5LwHFZXCiOjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwwGrq/btsIs2VCSxm/LbFNulV61e5LwHFZXCiOjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwwGrq/btsIs2VCSxm/LbFNulV61e5LwHFZXCiOjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwwGrq%2FbtsIs2VCSxm%2FLbFNulV61e5LwHFZXCiOjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;92&quot; data-filename=&quot;32.png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우에는 각 팀원들에게 개인 계정을 발급해주고, 이슈가 발생하면 1차적으로 파악한 후 담당 팀원에게 간략한 설명과 Sentry issue url을 첨부해 슬랙으로 전송했습니다. 모니터링 담당으로서 서비스 전반적으로 발생하는 에러를 빠르게 파악할 수 있어 좋았고, 각 담당자들은 더 촘촘한 디버깅이 가능해서 만족도가 높았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EmJjs/btsIr8IV1AC/aqU0dgPurS1yOx8rMKKXU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EmJjs/btsIr8IV1AC/aqU0dgPurS1yOx8rMKKXU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EmJjs/btsIr8IV1AC/aqU0dgPurS1yOx8rMKKXU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEmJjs%2FbtsIr8IV1AC%2FaqU0dgPurS1yOx8rMKKXU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2866&quot; height=&quot;774&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B2gtD/btsItDA5jmg/dKDRnpIXWNRH0Wm9xZYcr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B2gtD/btsItDA5jmg/dKDRnpIXWNRH0Wm9xZYcr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B2gtD/btsItDA5jmg/dKDRnpIXWNRH0Wm9xZYcr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB2gtD%2FbtsItDA5jmg%2FdKDRnpIXWNRH0Wm9xZYcr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2866&quot; height=&quot;774&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;35.png&quot; data-origin-width=&quot;2826&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA4yhY/btsItTcy9W2/tRvYlhtDEFMjkNkZKWmka0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA4yhY/btsItTcy9W2/tRvYlhtDEFMjkNkZKWmka0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA4yhY/btsItTcy9W2/tRvYlhtDEFMjkNkZKWmka0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA4yhY%2FbtsItTcy9W2%2FtRvYlhtDEFMjkNkZKWmka0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2826&quot; height=&quot;868&quot; data-filename=&quot;35.png&quot; data-origin-width=&quot;2826&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;36.png&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ueD7/btsIsLGvZWR/OS4izqpUaSiPhuEolDwYO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ueD7/btsIsLGvZWR/OS4izqpUaSiPhuEolDwYO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ueD7/btsIsLGvZWR/OS4izqpUaSiPhuEolDwYO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ueD7%2FbtsIsLGvZWR%2FOS4izqpUaSiPhuEolDwYO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2878&quot; height=&quot;880&quot; data-filename=&quot;36.png&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 만족도가 높은 개선사항들이었습니다. 다만 시스템 구동을 위한 최소 사양이 ec2 xlarge인 만큼 유지비용이 굉장히 올라갔고, 현재 활용중인 Sentry 기능이 전부 중 극히 일부이기 때문에 활용법에 대해 조금 더 연구가 필요할 것 같습니다. 또, 서비스 운영의 안정기에 들어간만큼 추가로 SRE 시스템들을 도입하기 보단 현재 시스템을 활용하는 방안을 연구하거나, 시스템의 규모와 비용을 최적화하는 방향으로 나아갈 예정입니다.&lt;/p&gt;</description>
      <category>   Activity/Project</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/70</guid>
      <comments>https://way-code.tistory.com/entry/BE-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%B4-%EC%95%88%EC%8B%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%82%90%EC%82%90-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0-2%ED%83%84-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98-%EA%B2%BD%EB%B3%B4-Sentry#entry70comment</comments>
      <pubDate>Wed, 10 Jul 2024 00:40:56 +0900</pubDate>
    </item>
    <item>
      <title>개발자들이 안심할 수 있는 삐삐 서비스 만들기&amp;nbsp;1탄 (프로메테우스, 그라파나)</title>
      <link>https://way-code.tistory.com/entry/%EC%82%90%EC%82%90-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%EA%B8%B0-1-%ED%94%84%EB%A1%9C%EB%A9%94%ED%85%8C%EC%9A%B0%EC%8A%A4-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98-%EB%8F%84%EC%9E%85</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가기 전에&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 런칭 초기 유저가 급격히 유입되었지만, 사소한 버그가 유저 경험에 악영향을 미쳐 유저 이탈이 발생하는 것을 종종 보았습니다. 실제로 삐삐 서비스(&lt;a href=&quot;https://bibbi.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bibbi.app/&lt;/a&gt;) 배포 이후 발생한 버그를 뒤늦게 발견하고 일하던 중 급히 뛰어나가 전화를 돌리며 문제를 파악하고 핫픽스 하는 경험을 했고....   이로인해 팀 내에서 서비스 모니터링이 필요하다는 의견이 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 서비스를 운영하고 많은 양의 트래픽이 발생하면서 기존의 스프링 부트 터미널 로그로는 에러 추적이나 트래픽 관측이 불가능하다고 느꼈습니다. 실시간으로 많은 양의 로그가 쌓이고 있기 때문에 터미널에서 plain text로부터 원하는 데이터를 찾는 것은 굉장히 어려웠고, 24시간 로그를 확인할 수 없는 노릇이니 트래픽을 모니터링할 수 있는 대시보드와 경보 시스템도 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다양한 모니터링 시스템을 추가해왔는데, 당시에는 몰랐지만 이 작업들이 말로만 듣던 SRE(Site Reliability Engineering)이었던 것 같습니다. 모니터링 시스템을 통해 예외와 에러를 감지하고, 감지한 데이터들을 담당 팀원에게 전달하여 디버깅할 수 있도록 도와주었습니다. 본 게시글들을 통해 모니터링 시스템을 구축하는 과정과 저만의 노하우를 공유해보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로메테우스 &amp;amp; 그라파나 도입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 모니터링 시스템은 프로메테우스 &amp;amp; 그라파나를 통한 트래픽 모니터링 대시보드였습니다. 추가적인 서비스 유지 비용없이 클라우드 크레딧으로 구현하길 원했기 때문에, 오픈소스이면서 스프링 부트와 호환성이 높은 프로메테우스와 PromQL만 알면 다양한 대시보드를 손쉽게 꾸밀 수 있으며 프로메테우스와 호환성이 좋은 그라파나를 선택하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 클라우드 크레딧과 비용 관계에 따라 인프라를 자주 이관할 것이 예상되었기 때문에, 컨테이너 기반으로 시스템들을 관리했으며, 불필요하게 시간이 소요되는 네트워크 설정은 솔루션에 맡기기 위해 Nginx Proxy Manager을 도입했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;monitoring-architecture-1-1.png&quot; data-origin-width=&quot;1995&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK4LDt/btsIjJuOkGx/8A4l3kiAxBZ8L5BkAQzB6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK4LDt/btsIjJuOkGx/8A4l3kiAxBZ8L5BkAQzB6K/img.png&quot; data-alt=&quot;삐삐 서비스의 프로메테우스 &amp;amp;amp; 그라파나 시스템 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK4LDt/btsIjJuOkGx/8A4l3kiAxBZ8L5BkAQzB6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK4LDt%2FbtsIjJuOkGx%2F8A4l3kiAxBZ8L5BkAQzB6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1995&quot; height=&quot;882&quot; data-filename=&quot;monitoring-architecture-1-1.png&quot; data-origin-width=&quot;1995&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;삐삐 서비스의 프로메테우스 &amp;amp; 그라파나 시스템 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx Proxy Manager 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드를 배포해본 사람이라면 HTTPS 연결을 위해 한 번쯤은 Let's Encrypt나 ACME와 씨름해보았을 것 입니다. 하지만 모니터링 백오피스를 위해 프록시, SSL에 시간을 쓰긴 아깝죠. Nginx Proxy Manager는 nginx의 GUI 솔루션으로서, nginx.conf 를 통한 프록시 설정을 간단한 UI를 통해 가능케하며 Let's Encrypt기반 인증서 설정이 원클릭으로 가능합니다. 이에 대한 자세한 설정 가이드는 이후 별도의 게시글로 정리해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. docker-compose-nginx.yml 작성 및 실행&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;version: '3.8'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx
    restart: always
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./nginx/data:/data
      - ./nginx/letsencrypt:/etc/letsencrypt

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;restart: always : 인스턴스가 재부팅하더라도 프록시 컨테이너가 늘 재실행하도록 설정&lt;/li&gt;
&lt;li&gt;ports: - '81:81' : Nginx Proxy Manager 콘솔 디폴트 포트&lt;/li&gt;
&lt;li&gt;volumes: - ~ : 컨테이너 이미지를 내려받을 때 자동으로 다운로드되는 패키지 파일들 (매핑하는 좌측 소스 디렉토리는 자유롭게 변경 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Let's Encrypt 기반 SSL 인증서 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7YZI/btsIlbX3jFU/mnYw6dHIJZFOLl7g5lGwk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7YZI/btsIlbX3jFU/mnYw6dHIJZFOLl7g5lGwk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7YZI/btsIlbX3jFU/mnYw6dHIJZFOLl7g5lGwk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD7YZI%2FbtsIlbX3jFU%2FmnYw6dHIJZFOLl7g5lGwk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;370&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 호스트 프록시 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx3uzH/btsIja0Axo9/juu6X8k15l2sf2YVWukH6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx3uzH/btsIja0Axo9/juu6X8k15l2sf2YVWukH6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx3uzH/btsIja0Axo9/juu6X8k15l2sf2YVWukH6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx3uzH%2FbtsIja0Axo9%2Fjuu6X8k15l2sf2YVWukH6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;951&quot; height=&quot;547&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로메테우스 &amp;amp; 그라파나 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함께 사용하는 프로메테우스와 그라파나는 하나의 docker compose 파일에 관리하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ./monitor, ./monitor/prometheus.yml 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. docker-compose-monitor.yml 작성 및 실행&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: '3'

services:
        prometheus:
                image: prom/prometheus
                container_name: prometheus
                volumes:
                        - ./monitor/prometheus.yml:/prometheus/prometheus.yml:ro
                        - /root/ecs-discovery/ecs_file_sd.yml:/etc/prometheus/bibbi.yml:ro
                ports:
                        - 8080:9090
                command:
                        - &quot;--web.enable-lifecycle&quot;
                restart: always
                networks:
                        - promnet
                user: root

        grafana:
                image: grafana/grafana
                container_name: grafana
                volumes:
                        - ./monitor/grafana-volume:/var/lib/grafana
                restart: always
                environment:
                    GF_SERVER_ROOT_URL: &amp;lt;https://grafana.no5ing.kr:443&amp;gt;
                    GF_SERVER_DOMAIN: grafana.no5ing.kr
                networks:
                        - promnet
                ports:
                        - 8070:3000
                user: root

networks:
        promnet:
                driver: bridge

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;volumnes: ./monitor/prometheus.yml:~~ : 메트릭 포링 주기 등의 프로메테우스 설정값을 담은 파일&lt;/li&gt;
&lt;li&gt;ports: -8080:9090 ports: -8070:3000 : 프로메테우스의 디폴트 포트는 9090, 그라파나는 3000&lt;/li&gt;
&lt;li&gt;networks: promnet: driver: bridge : 컨테이너 명을 통해 컨테이너에 접속할 수 있도록, promnet이라는 bridge Docker network를 생성하고 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프로메테우스 콘솔 접속&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9I3lG/btsIhWIQ3rq/ZB1565K5wrMHgWpFqt4kv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9I3lG/btsIhWIQ3rq/ZB1565K5wrMHgWpFqt4kv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9I3lG/btsIhWIQ3rq/ZB1565K5wrMHgWpFqt4kv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9I3lG%2FbtsIhWIQ3rq%2FZB1565K5wrMHgWpFqt4kv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;1467&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 그라파나 콘솔 접속&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;  반드시 디폴트 계정 변경하세요  &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;디폴트 아이디 : admin / 디폴트 비밀번호 : admin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG204O/btsIkjWGOdv/MmnCYIKdu0ntANVmrKXcsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG204O/btsIkjWGOdv/MmnCYIKdu0ntANVmrKXcsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG204O/btsIkjWGOdv/MmnCYIKdu0ntANVmrKXcsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG204O%2FbtsIkjWGOdv%2FMmnCYIKdu0ntANVmrKXcsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;1467&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 모니터링 환경 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템 설치는 끝났으니, 스프링에서 모니터링 할 매트릭을 제공하기 위한 환경설정을 할 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot Actuator를 통해 HTTP나 JMX를 이용한 매트릭을 생성합니다. 그리고 생성된 매트릭들을 프로메테우스와 같은 다양한 매트릭 수집 소프트웨어들이 폴링할 수 있도록 추상화된 파사드를 제공하는 Micrometer를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. build.gradle 에 의존성 추가&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티모듈의 경우, 서버 실행 주체가 되는 모듈(API, Gateway)에 추가하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-actuator'  // actuator
implementation 'io.micrometer:micrometer-registry-prometheus'  // micrometer for prometheus

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. application.yaml 에 Actuator 환경 설정&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: &quot;*&quot;

---
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면, https://DOMAIN/actuator 에서 각 매트릭을 확인할 수 있는 actuator url들에 대한 정보를 제공합니다. 필요한 매트릭 외에 공개하지 않는 것이 보안적으로 안전하므로, 화이트리스트 방식으로 필요한 url을 설정하는 것을 추천합니다. 저의 경우는 instance server 정보, health check, prometheus 매트릭이 필요하므로 3가지 url만 노출시켰습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR8SXY/btsIkOvfIAC/8P0bVHN66LmzUsKSO4fieK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR8SXY/btsIkOvfIAC/8P0bVHN66LmzUsKSO4fieK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR8SXY/btsIkOvfIAC/8P0bVHN66LmzUsKSO4fieK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR8SXY%2FbtsIkOvfIAC%2F8P0bVHN66LmzUsKSO4fieK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;1467&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정이 완료되었습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dlowt/btsIk9FWbBy/VJJE748W3XuooLLr5UiPdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dlowt/btsIk9FWbBy/VJJE748W3XuooLLr5UiPdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dlowt/btsIk9FWbBy/VJJE748W3XuooLLr5UiPdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDlowt%2FbtsIk9FWbBy%2FVJJE748W3XuooLLr5UiPdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;1467&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로메테우스 타켓 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 프로메테우스로 돌아가 매트릭 환경설정이 완료된 서버를 모니터링 타켓으로 설정하는 절차를 마치면, 프로메테우스가 정상적으로 매트릭을 폴링할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;./monitor/prometheus.yaml 에서 매트릭 수집 타켓 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;global:
  scrape_interval: 10s
  evaluation_interval: 10s

scrape_configs:
  - job_name: 'bibbi-prod-was'   // 모니터링 Target 이름
    metrics_path: '/actuator/prometheus'   // 매트릭 url
    static_configs:   // 정적 모니터링 대상
      - targets: ['api.no5ing.kr']   // Target 목록

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 생성한 프로메테우스 설정파일에 위와 같이 설정한 후, 프로메테우스 컨테이너를 재기동하면(docker compose -f ./docker-compose-monitor.yml up -d) 변경된 내역이 반영됩니다. 프로메테우스 콘솔에서 Status &amp;gt; Targets 을 들어가서 설정한 엔드포인트가의 상태가 'up'이면 정상적으로 폴링되고 있는 것입니다. Graph에서 아무 매트릭을 입력하고 조회하고 정상적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PGX4e/btsIjxOOwDo/CCB0A6PHwTDcXeANShmdHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PGX4e/btsIjxOOwDo/CCB0A6PHwTDcXeANShmdHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PGX4e/btsIjxOOwDo/CCB0A6PHwTDcXeANShmdHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPGX4e%2FbtsIjxOOwDo%2FCCB0A6PHwTDcXeANShmdHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1141&quot; height=&quot;379&quot; data-filename=&quot;7.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그라파나 대시보드 구성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템을 도입하는 것보다 중요한 것은 어떻게 잘 활용하냐일 것입니다. 그라파나를 구성하는 방법은 두가지인데, 1. 주어진 시각화 도구를 이용해 직접 대시보드를 구성하는 것이고, 2. 다른 사랑의 대시보드를 import 하는 것입니다. 물론 import한 대시보드를 커스터마이징하는 것도 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;3426&quot; data-origin-height=&quot;1312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJU3Fp/btsIjdbYkl8/37GV6JzA1uig0RvGSz1sJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJU3Fp/btsIjdbYkl8/37GV6JzA1uig0RvGSz1sJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJU3Fp/btsIjdbYkl8/37GV6JzA1uig0RvGSz1sJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJU3Fp%2FbtsIjdbYkl8%2F37GV6JzA1uig0RvGSz1sJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3426&quot; height=&quot;1312&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;3426&quot; data-origin-height=&quot;1312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Grafana Labs&lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(  Link)&lt;/a&gt;&lt;/span&gt;&amp;nbsp;이나 Github에서 다양한 대시보드를 url이나 json 형태로 import 할 수 있습니다. 저의 경우에는 Grafana Labs에서 가장 마음에 드는 대시보드를 적용시켜 PromQL 활용법을 연습하고, 저희 시스템에 필요한 대시보드로 조금씩 커스터마이징해 나갔습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;3824&quot; data-origin-height=&quot;2354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTYoCp/btsIkd3mMqz/VUcG4HoJdEKUwEKK4qime0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTYoCp/btsIkd3mMqz/VUcG4HoJdEKUwEKK4qime0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTYoCp/btsIkd3mMqz/VUcG4HoJdEKUwEKK4qime0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTYoCp%2FbtsIkd3mMqz%2FVUcG4HoJdEKUwEKK4qime0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3824&quot; height=&quot;2354&quot; data-filename=&quot;22.png&quot; data-origin-width=&quot;3824&quot; data-origin-height=&quot;2354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 제가 커스터마이징한 대시보드를 공유하고, 이를 간단히 설명하여 핵심 PromQL을 설명해보겠습니다. 대시보드 json 파일은 제 깃허브에서 확인할 수 있습니다.&lt;a href=&quot;https://github.com/Kwon770/SRE-practice/blob/main/grafana-dashboard-bibbi.json&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/Kwon770/SRE-practice/blob/main/grafana-dashboard-bibbi.json&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Github 링크)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YM1av/btsIj4TaR2A/rmfIUuXkhrXdGtMxaHF9DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YM1av/btsIj4TaR2A/rmfIUuXkhrXdGtMxaHF9DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YM1av/btsIj4TaR2A/rmfIUuXkhrXdGtMxaHF9DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYM1av%2FbtsIj4TaR2A%2FrmfIUuXkhrXdGtMxaHF9DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1832&quot; height=&quot;1527&quot; data-filename=&quot;33.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PromQL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PromQL(Prometheus Query Language)은 Prometheus에서 사용되는 쿼리 언어로, 시계열 데이터를 쿼리하고 분석하는데 사용됩니다. 따라서 프로메테우스로 부터 받아온 데이터를 분석하고 시각화하도록 그라파나를 지원합니다. 다양한 문법이 존재하지만 increase 와 rate 의 활용법을 이해한다면 다양한 시각화를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Increase (API 요청량_2XX 정상 응답만)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;44.png&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0KM7V/btsIk8Agi2n/NrZ5JeS9K1yaOlfaLTuIy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0KM7V/btsIk8Agi2n/NrZ5JeS9K1yaOlfaLTuIy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0KM7V/btsIk8Agi2n/NrZ5JeS9K1yaOlfaLTuIy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0KM7V%2FbtsIk8Agi2n%2FNrZ5JeS9K1yaOlfaLTuIy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1065&quot; height=&quot;481&quot; data-filename=&quot;44.png&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;increase(http_server_requests_seconds_count{instance=&quot;$instance&quot;, application=&quot;$application&quot;, uri!~&quot;.*actuator.*&quot;, uri!~&quot;.*swagger*.*&quot;, uri!~&quot;.*api-docs*.*&quot;, status=~&quot;2.*&quot;}[30s])
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http_server_requests_seconds_count : 서버에 들어온 http 요청의 누적 갯수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로메테우스가 수집한 매트릭을 그대로 입력할 경우 선택한 시각화 그래프에 맞게 표현됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;55.png&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTlJQ/btsIkOaXs4t/uYwrJitwkuup70YgTcPuBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTlJQ/btsIkOaXs4t/uYwrJitwkuup70YgTcPuBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTlJQ/btsIkOaXs4t/uYwrJitwkuup70YgTcPuBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTlJQ%2FbtsIkOaXs4t%2FuYwrJitwkuup70YgTcPuBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1007&quot; height=&quot;441&quot; data-filename=&quot;55.png&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http_server_requests_seconds_count&lt;b&gt;{instance=&quot;$instance&quot;, application=&quot;$application&quot;, uri!~&quot;.**actuator.**&quot;, uri!~&quot;.**swagger**.**&quot;, uri!~&quot;.*api-docs*.**&quot;, status=~&quot;2.*&quot;}&lt;/b&gt; : 설정된 인스턴스, 어플리케이션에서 url에 actuator, swagger, api-docs를 포함하지 않고 응답 결과가 2로 시작한 http 요청 누적 갯수 (실제 클라이언트의 요청만 확인하기 위해 개발자용 url과 매트릭을 수집하기 위한 actuator url을 제외함)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매트릭에 중괄호를 붙여 데이터에 다양한 필터를 적용할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;$CONDITION&lt;/b&gt; : 상단바에서 유저가 선택한 조건 기준 (인스턴스, 어플리케이션, 조회 interval 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;!~&lt;/b&gt; : 같지 않음, &lt;b&gt;=~&lt;/b&gt; : 같음, &lt;b&gt;.*&lt;/b&gt; : 0개 이상의 임의의 문자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;66.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0K0L/btsIlcbA6Zh/Tkniioruy1oR9jAALtKJa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0K0L/btsIlcbA6Zh/Tkniioruy1oR9jAALtKJa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0K0L/btsIlcbA6Zh/Tkniioruy1oR9jAALtKJa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0K0L%2FbtsIlcbA6Zh%2FTkniioruy1oR9jAALtKJa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1038&quot; height=&quot;460&quot; data-filename=&quot;66.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;increase&lt;/b&gt;(http_server_requests_seconds_count{instance=&quot;$instance&quot;, application=&quot;$application&quot;, uri!~&quot;.actuator.&quot;, uri!~&quot;.swagger.&quot;, uri!~&quot;.api-docs.&quot;, status=~&quot;2.*&quot;}&lt;b&gt;[30s]&lt;/b&gt;) : &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;30초 간격으로 http 요청 갯수의 변화량 ( = 30초마다 발생한 http 요청 수)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fbf3db;&quot; data-token-index=&quot;0&quot;&gt;특정 시간 범위 내에서 주어진 데이터의 증가량을 계산합니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fbf3db;&quot; data-token-index=&quot;0&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;77.png&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmfnoC/btsIiUXU4aS/y85bDOfm0iGr9p7fYxtZX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmfnoC/btsIiUXU4aS/y85bDOfm0iGr9p7fYxtZX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmfnoC/btsIiUXU4aS/y85bDOfm0iGr9p7fYxtZX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmfnoC%2FbtsIiUXU4aS%2Fy85bDOfm0iGr9p7fYxtZX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1042&quot; height=&quot;456&quot; data-filename=&quot;77.png&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Increase 응용 (Status code 비율)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;increase와 간격을 응용하면 다음과 같은 대시보드도 만들 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sum(increase(http_server_requests_seconds_count{application=&quot;$application&quot;, uri!~&quot;.*actuator.*&quot;, uri!~&quot;.*swagger*.*&quot;, uri!~&quot;.api-docs.*&quot;, status=&quot;200&quot;}[$__range]))&lt;/li&gt;
&lt;li&gt;sum(increase(http_server_requests_seconds_count{application=&quot;$application&quot;, uri!~&quot;.*actuator.*&quot;, uri!~&quot;.*swagger*.*&quot;, uri!~&quot;.api-docs.*&quot;, status=&quot;204&quot;}[$__range]))&lt;/li&gt;
&lt;li&gt;sum(increase(http_server_requests_seconds_count{application=&quot;$application&quot;, uri!~&quot;.*actuator.*&quot;, uri!~&quot;.*swagger*.*&quot;, uri!~&quot;.api-docs.*&quot;, status=&quot;400&quot;}[$__range]))&lt;/li&gt;
&lt;li&gt;&amp;hellip;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;10초, 1시간, 6월 첫째주 등 대시보드의 조회 시간을 다양하게 조절할 수 있는데, 설정한 시간 동안 발생한 http 의 status code 비율을 나타냅니다.&lt;/li&gt;
&lt;li&gt;increase()를 통해 url 별 요청 갯수를 조회하므로, sum() 을 통해 동일한 status code의 응답 갯수의 합계를 구합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;88.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9WRDp/btsIiirmJqT/un0aVRGJci4urqsQJqfZ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9WRDp/btsIiirmJqT/un0aVRGJci4urqsQJqfZ9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9WRDp/btsIiirmJqT/un0aVRGJci4urqsQJqfZ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9WRDp%2FbtsIiirmJqT%2Fun0aVRGJci4urqsQJqfZ9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;464&quot; data-filename=&quot;88.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Increase 응용 (업로드 된 게시글 수)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;increase(http_server_requests_seconds_count{application=&quot;$application&quot;, uri=&quot;/v1/posts&quot;, method=&quot;POST&quot;, status=&quot;200&quot;}[$__range])
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 기간동안 /v1/posts에 POST 요청을 보내고 200 정상응답을 받은 요청의 수 (업로드 된 게시글 수)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;  시계열 데이터의 변화량을 계산하는 쿼리이므로, 정량적인 데이터나 성과의 정확한 수치를 얻기위한 용도로 사용하는 것은 추천하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;99.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TVJPP/btsIiOXMolX/PgXS68VPGo4vskyWNOg84K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TVJPP/btsIiOXMolX/PgXS68VPGo4vskyWNOg84K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TVJPP/btsIiOXMolX/PgXS68VPGo4vskyWNOg84K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTVJPP%2FbtsIiOXMolX%2FPgXS68VPGo4vskyWNOg84K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;447&quot; data-filename=&quot;99.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rate (API 요청량_2XX 정상 응답만)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rate()[TIME] 는 일정 시간동안 시계열 데이터의 비율(변화율)을 계산하는데 사용됩니다. 주로 카운터 메트릭(지속적으로 증가하는 값, ex: HTTP 요청 수, 에러 수 등)의 변화율을 계산하여 주어진 시간동안 얼마나 변화했는지 나타냅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sum(rate(http_server_requests_seconds_count{application=&quot;$application&quot;, uri!~&quot;.actuator.&quot;, uri!~&quot;.swagger.&quot;, uri!~&quot;.api-docs.&quot;}[1m]))
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지난 1분 동안 HTTP 요청 비율&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqOeGa/btsIiPbkbNZ/cL9yFoKL5ghsnhUPYbR0C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqOeGa/btsIiPbkbNZ/cL9yFoKL5ghsnhUPYbR0C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqOeGa/btsIiPbkbNZ/cL9yFoKL5ghsnhUPYbR0C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqOeGa%2FbtsIiPbkbNZ%2FcL9yFoKL5ghsnhUPYbR0C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;480&quot; data-filename=&quot;10.png&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 런칭 후 대내외적으로 사용 후기를 전해들었기 때문에 저희가 출시한 서비스가 사용되고 있다는 것은 알고 있었습니다. 하지만 대시보드를 통해 얼마나 서비스가 사용되었는지 수치로 확인하고 실시간으로 반응하는 트래픽을 보면서 지난 몇 년간의 코딩 경험 중 가장 뿌듯한 시점이였던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삐삐 서비스에 대한 애정과 열정을 다시 한 번 불태울 수 있었고, 도입한 시스템을 잘 활용하고 발전하여 더 멋진 삐삐로 만들어 나가겠습니다.&lt;/p&gt;</description>
      <category>   Activity/Project</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/69</guid>
      <comments>https://way-code.tistory.com/entry/%EC%82%90%EC%82%90-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%EA%B8%B0-1-%ED%94%84%EB%A1%9C%EB%A9%94%ED%85%8C%EC%9A%B0%EC%8A%A4-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98-%EB%8F%84%EC%9E%85#entry69comment</comments>
      <pubDate>Tue, 2 Jul 2024 10:12:20 +0900</pubDate>
    </item>
    <item>
      <title>[JDBC] Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)</title>
      <link>https://way-code.tistory.com/entry/JDBC-Caused-by-javaxnetsslSSLHandshakeException-No-appropriate-protocol-protocol-is-disabled-or-cipher-suites-are-inappropriate</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사 업무 공부를 위해서는 로우레밸 기술(이라하지만 흔히들 레거시라고 부르는 기술)들을 공부하고 사이드 프로젝트는 최신 기술 및 버전을 사용하다 보니, 버저닝 관리 실패로 발생한 오류였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 날리는 과정에서 SSLHanndshakeException으로 인해 DB Connection 획득을 실패해 conn이 null인 에러가 발생했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717942560173&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The last packet successfully received from the server was 65 milliseconds ago.  The last packet sent successfully to the server was 61 milliseconds ago.
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
	at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
	at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:175)
	at com.mysql.jdbc.MysqlIO.negotiateSSLConnection(MysqlIO.java:4901)
	at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1659)
	at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1226)
	at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2194)
	at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2225)
	at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2024)
	at com.mysql.jdbc.ConnectionImpl.&amp;lt;init&amp;gt;(ConnectionImpl.java:779)
	at com.mysql.jdbc.JDBC4Connection.&amp;lt;init&amp;gt;(JDBC4Connection.java:47)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
	at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:389)
	at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
	at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:681)
	at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
	at util.DatabaseUtil.getConnection(DatabaseUtil.java:14)
	at com.hello.user.UserDAO.join(UserDAO.java:13)
	at org.apache.jsp.userJoinAction_jsp._jspService(userJoinAction_jsp.java:142)
	at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:583)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:465)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:383)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:331)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:583)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:212)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:156)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:181)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:156)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:679)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:617)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:934)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1698)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at java.base/sun.security.ssl.HandshakeContext.&amp;lt;init&amp;gt;(HandshakeContext.java:172)
	at java.base/sun.security.ssl.ClientHandshakeContext.&amp;lt;init&amp;gt;(ClientHandshakeContext.java:103)
	at java.base/sun.security.ssl.TransportContext.kickstart(TransportContext.java:247)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:448)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
	at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:160)
	... 49 more
java.lang.NullPointerException: Cannot invoke &quot;java.sql.Connection.prepareStatement(String)&quot; because &quot;conn&quot; is null
	at com.hello.user.UserDAO.join(UserDAO.java:14)
	at org.apache.jsp.userJoinAction_jsp._jspService(userJoinAction_jsp.java:142)
	at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:583)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:465)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:383)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:331)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:583)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:212)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:156)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:181)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:156)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:679)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:617)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:934)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1698)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:840)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;server와 client 간에 사용하는 SSL/TLS 버전이 맞지 않기 때문이라고 하는데, 관련 자료를 찾아보니 javaMail과 관련된 내용 밖에 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 내 경우에는 Tomcat server와 DB 연결 중에 발생한 오류인데, 네트워크 수준으로 생각하면 이 관계도 server - client라고 볼 수 있으니 JDBC와 MySQL 버전 문제가 아닐까 의심이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  MySQL은 Docker를 통해 여러 용도로 사용하고 있어 비교적 최신 버전인 8.0.35이지만, JDBC Driver는 강의에서 제공한 5.1.43 버전이였다. &lt;b&gt;&lt;a href=&quot;https://dev.mysql.com/downloads/connector/j/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MySQL Community Connector/J&lt;/a&gt;에서 MySQL 8.0.35와 호환되는 드라이버를 설치하니 해결되었다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>   Backend/SpringBoot</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/68</guid>
      <comments>https://way-code.tistory.com/entry/JDBC-Caused-by-javaxnetsslSSLHandshakeException-No-appropriate-protocol-protocol-is-disabled-or-cipher-suites-are-inappropriate#entry68comment</comments>
      <pubDate>Sun, 9 Jun 2024 23:16:38 +0900</pubDate>
    </item>
    <item>
      <title>테스트코드 실행 시 버전 에러 - Unsupported class file major version, Error while instrumenting with JaCoCo</title>
      <link>https://way-code.tistory.com/entry/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%8B%9C-%EB%B2%84%EC%A0%84-%EC%97%90%EB%9F%AC-Unsupported-class-file-major-version-Error-while-instrumenting-with-JaCoCo</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;새로 추가된 기능들이 있어서, 오랜만에 통합 테스트를 추가하고 실행하는데 에러가 발생했다. 스프링 어플리케이션 자체 실행에는 문제가 없고 테스트는 정상적으로 실행되지만, 테스트 로그에는 엄청난 양의 에러 로그가 남고 Build 결과에도 에러가 가득했다.&lt;/p&gt;
&lt;h1&gt;트러블 슈팅&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;현상 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 로그가 엄청나게 길지만 결국 아래 로그가 반복되고 있었다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;java.lang.instrument.IllegalClassFormatException: Error while instrumenting sun/security/ec/SunEC$1 with JaCoCo 0.8.8.202204050719/5dcf34a.
    at org.jacoco.agent.rt.internal_b6258fc.CoverageTransformer.transform(CoverageTransformer.java:94)
    at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:244)
    at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:610)
~~~~
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.io.IOException: Error while instrumenting sun/security/ec/SunEC$1 with JaCoCo 0.8.8.202204050719/5dcf34a.
    at org.jacoco.agent.rt.internal_b6258fc.core.instr.Instrumenter.instrumentError(Instrumenter.java:161)
    at org.jacoco.agent.rt.internal_b6258fc.core.instr.Instrumenter.instrument(Instrumenter.java:111)
    at org.jacoco.agent.rt.internal_b6258fc.CoverageTransformer.transform(CoverageTransformer.java:92)
    ... 176 more
Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 65
    at org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader.&amp;lt;init&amp;gt;(ClassReader.java:199)
    at org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader.&amp;lt;init&amp;gt;(ClassReader.java:180)
    at org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader.&amp;lt;init&amp;gt;(ClassReader.java:166)
    at org.jacoco.agent.rt.internal_b6258fc.core.internal.instr.InstrSupport.classReaderFor(InstrSupport.java:280)
    at org.jacoco.agent.rt.internal_b6258fc.core.instr.Instrumenter.instrument(Instrumenter.java:77)
    at org.jacoco.agent.rt.internal_b6258fc.core.instr.Instrumenter.instrument(Instrumenter.java:109)
    ... 177 more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 문장 정도가 에러의 핵심으로 보인다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;java.lang.instrument.IllegalClassFormatException: Error while instrumenting sun/security/ec/SunEC$1 with JaCoCo 0.8.8.202204050719/5dcf34a.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 65&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 상으로는 JaCoCO가 원인인 것 같아 해당 로그를 통해 찾아봤지만 그럴듯한 내용은 전혀 없었다. &lt;code&gt;Unsupported class ~&lt;/code&gt; 를 통해서 찾아보니, 스프링 부트와 맞지 않은 JDK를 사용해서라고 하지만 스프링 어플리케이션 실행에는 문제가 없고 프로젝트 생성할 때 Spring boot 3 버전에 맞는 JDK로 설치했다. 혹시 몰라 JDK를 변경해봤는데, 오히려 더 낮은 버전의 JDK로 변경하자 해결되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK 20 적용 &amp;rarr; ❌ Unsupported class file major version 64&lt;/li&gt;
&lt;li&gt;JDK 21 적용 &amp;rarr; ❌ Unsupported class file major version 65&lt;/li&gt;
&lt;li&gt;JDK 17 &amp;rarr; ✅ 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인이 파악되지 않아 혼란스러웠는데, 특히 JaCoCo가 너무 뜬금없이 로그에 있어서 더 혼란스러웠다. 고민하던 중 JaCoCO와 호환되지 않는 JDK라는 뜻은 아닐까 의심이 들어서 JacCoCo 호환성을 찾아봤더니 정답이였다. 사용하고 있는 0.8.8에서 JDK 17, 18을 지원하기 시작했으니, 지원하지 않는 JDK를 사용하고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-13 18.15.54.png&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTSJm6/btsGB4UHb3U/aBKARGlMxBrTISQtmISZhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTSJm6/btsGB4UHb3U/aBKARGlMxBrTISQtmISZhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTSJm6/btsGB4UHb3U/aBKARGlMxBrTISQtmISZhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTSJm6%2FbtsGB4UHb3U%2FaBKARGlMxBrTISQtmISZhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2272&quot; height=&quot;1500&quot; data-filename=&quot;스크린샷 2024-04-13 18.15.54.png&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;해결 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 SDK로 사용되는 JDK 버전을 인텔리지에서 변경하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;File &amp;gt; Project Structure &amp;gt; Project Settings &amp;gt; Project&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SDK&lt;/code&gt; 변경&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Language level&lt;/code&gt; 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-03 16.06.14.png&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjwycT/btsGAdk5rd7/zVcnm8yDhSAEqKmUsybUo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjwycT/btsGAdk5rd7/zVcnm8yDhSAEqKmUsybUo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjwycT/btsGAdk5rd7/zVcnm8yDhSAEqKmUsybUo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjwycT%2FbtsGAdk5rd7%2FzVcnm8yDhSAEqKmUsybUo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2272&quot; height=&quot;1914&quot; data-filename=&quot;스크린샷 2024-04-03 16.06.14.png&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. &lt;code&gt;Intellij IDEA &amp;gt; Settings &amp;gt; Build, Execution, Deployment &amp;gt; Build Tools &amp;gt; Gradle &amp;gt; Gradle JVM&lt;/code&gt; 변경&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-03 16.05.27.png&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baxCnG/btsGBguAm7U/9OwqK3VsKVxu5Id7mrPTlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baxCnG/btsGBguAm7U/9OwqK3VsKVxu5Id7mrPTlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baxCnG/btsGBguAm7U/9OwqK3VsKVxu5Id7mrPTlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaxCnG%2FbtsGBguAm7U%2F9OwqK3VsKVxu5Id7mrPTlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2206&quot; height=&quot;1650&quot; data-filename=&quot;스크린샷 2024-04-03 16.05.27.png&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 해결된다.  &lt;br /&gt;JDK 버전 호환 트러블슈팅에 대한 자료는 있지만, JaCoCo로 인해 JDK 버전 호환에 대한 자료는 없어서 작성해보았다.&lt;/p&gt;</description>
      <category>   Backend/SpringBoot</category>
      <category>jacoco</category>
      <category>java</category>
      <category>JDK</category>
      <category>Spring Boot</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/67</guid>
      <comments>https://way-code.tistory.com/entry/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%8B%A4%ED%96%89-%EC%8B%9C-%EB%B2%84%EC%A0%84-%EC%97%90%EB%9F%AC-Unsupported-class-file-major-version-Error-while-instrumenting-with-JaCoCo#entry67comment</comments>
      <pubDate>Sat, 13 Apr 2024 18:18:01 +0900</pubDate>
    </item>
    <item>
      <title>@DataJpaTest 테스트 중 다양한 데이터베이스 및 쿼리 예외 해결하기</title>
      <link>https://way-code.tistory.com/entry/DataJpaTest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%91-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%B0%8F-%EC%BF%BC%EB%A6%AC-%EC%98%88%EC%99%B8-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;@DataJpaTest를 이용한 Repository 테스트 중 본 어플리케이션 실행에서 발생하지 않던 DDL에서 예외가 발생했다. H2 &amp;harr;️ MySQL 문법 차이로 인해 테이블이 제대로 생성되지 않는 것으로 보여서 &lt;code&gt;application-test.properties&lt;/code&gt; 를 계속 수정했지만 해결되지 않았다. 문득, 지난 번에도 &lt;code&gt;@DataJpaTest&lt;/code&gt; 테스트에서 발생하는 에러를 &lt;code&gt;@AutoConfigureTestDatabase&lt;/code&gt;으로 해결한 기억이나서 시도하였고 해결되었다. 그때도 디버깅 과정 기록을 미뤄두고 있었는데, 이 참에 블로깅하고 장기 기억으로 가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;문제 분석&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시도할 때마다 다양한 예외가 발생하고 인터넷에도 다양한 예외 케이스가 있는데, 데이터베이스 설정 문제이거나 DDL 혹은 SQL 문법 오류다. 공통적으로 DataSoruce로 인해 발생한 것인데, 본 어플리케이션은 정상적으로 됬는데, 왜 테스트할 때만 문제가 발생하는걸까? &lt;b&gt;&lt;code&gt;@DataJpaTest&lt;/code&gt;를 자세히 살펴보자&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;CommandAcceptanceException: Error executing DDL - ... (this database is empty)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JdbcSQLSyntaxErrorException: Syntax error in SQL statement ... expected &quot;identifier&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;/**  
 * Annotation for a JPA test that focuses &amp;lt;strong&amp;gt;only&amp;lt;/strong&amp;gt; on JPA components.  
 * &amp;lt;p&amp;gt;  
 * Using this annotation will disable full auto-configuration and instead apply only  
 * configuration relevant to JPA tests. * &amp;lt;p&amp;gt;  
 * By default, tests annotated with {@code @DataJpaTest} are transactional and roll back  
 * at the end of each test. They also use an embedded in-memory database (replacing any * explicit or usually auto-configured DataSource). The * {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase} annotation can be used to  
 * override these settings. * &amp;lt;p&amp;gt;  
 * SQL queries are logged by default by setting the {@code spring.jpa.show-sql} property  
 * to {@code true}. This can be disabled using the {@link DataJpaTest#showSql() showSql}  
 * attribute. * &amp;lt;p&amp;gt;  
 * If you are looking to load your full application configuration, but use an embedded  
 * database, you should consider {@link SpringBootTest @SpringBootTest} combined with  
 * {@link AutoConfigureTestDatabase @AutoConfigureTestDatabase} rather than this  
 * annotation. * &amp;lt;p&amp;gt;  
 * When using JUnit 4, this annotation should be used in combination with  
 * {@code @RunWith(SpringRunner.class)}.  
 * * @author Phillip Webb  
 * @author Artsiom Yudovin  
 * @author Scott Frederick  
 * @since 1.4.0  
 * @see AutoConfigureDataJpa  
 * @see AutoConfigureTestDatabase  
 * @see AutoConfigureTestEntityManager  
 * @see AutoConfigureCache  
 */  
@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@BootstrapWith(DataJpaTestContextBootstrapper.class)  
@ExtendWith(SpringExtension.class)  
@OverrideAutoConfiguration(enabled = false)  
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)  
@Transactional  
@AutoConfigureCache  
@AutoConfigureDataJpa  
@AutoConfigureTestDatabase  
@AutoConfigureTestEntityManager  
@ImportAutoConfiguration  
public @interface DataJpaTest {
// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석에도 잘 설명되어 있는 &lt;code&gt;@DataJpaTest&lt;/code&gt; 의 특성은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;full application context를 로드하지 않고 JPA 테스트와 관련된 configuration만 활성화하는 테스트 어노테이션&lt;/li&gt;
&lt;li&gt;Repository 계층에 대한 격리된 테슽 환경을 설정하는데 유용&lt;/li&gt;
&lt;li&gt;임베디드 인메모리 데이터베이스를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 실행할 때도 H2 데이터베이스를 사용하고 있는데, 뭔가 다른걸까? &lt;code&gt;The @AutoConfigureTestDatabase annotation can be used to override these settings.&lt;/code&gt; 라고 하니, &lt;code&gt;@AutoConfigureTestDatabase&lt;/code&gt; 을 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target({ ElementType.TYPE, ElementType.METHOD })  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@ImportAutoConfiguration  
@PropertyMapping(&quot;spring.test.database&quot;)  
public @interface AutoConfigureTestDatabase {  

    /**  
     * Determines what type of existing DataSource bean can be replaced.     * @return the type of existing DataSource to replace  
     */    @PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)  
    Replace replace() default Replace.ANY;  

    /**  
     * The type of connection to be established when {@link #replace() replacing} the  
     * DataSource. By default, will attempt to detect the connection based on the     * classpath.     * @return the type of connection to use  
     */    
     EmbeddedDatabaseConnection connection() default EmbeddedDatabaseConnection.NONE;  

    /**  
     * What the test database can replace.     
     * */    
     enum Replace {  

       /**  
        * Replace the DataSource bean whether it was auto-configured or manually defined.        
        * */       
        ANY,  

       /**  
        * Only replace the DataSource if it was auto-configured.        
        * */       
        AUTO_CONFIGURED,  

       /**  
        * Don't replace the application default DataSource.        
        * */       
        NONE  

    }  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@PropertyMapping(&quot;spring.test.database&quot;)&lt;/code&gt;을 통해 값을 설정할 수 있지만, application property에서 따로 설정한 값이 없으니 디폴트 설정을 따른다. &lt;b&gt;replace 기본 전략은 ANY는 임베디드 무조건 인메모리 데이터베이스를 DataSource로 설정하고, connection 기본 전략은 NONE이지만 &lt;code&gt;EmbeddedDatabaseConnection&lt;/code&gt;, &lt;code&gt;TestDatabaseAutoConfiguration&lt;/code&gt; 등을 통해 ClassLoader기반으로 적합한 커넥션을 제공&lt;/b&gt;한다. 나의 경우는 H2 커넥션으로 설정되는 것을 로그에서 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-26 14.27.23 1.png&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw0lx0/btsGf93NQVi/35zjmP57WLtyNKBTNpzhTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw0lx0/btsGf93NQVi/35zjmP57WLtyNKBTNpzhTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw0lx0/btsGf93NQVi/35zjmP57WLtyNKBTNpzhTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw0lx0%2FbtsGf93NQVi%2F35zjmP57WLtyNKBTNpzhTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2652&quot; height=&quot;348&quot; data-filename=&quot;스크린샷 2024-01-26 14.27.23 1.png&quot; data-origin-width=&quot;2652&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;원인 파악&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;어? 어플리케이션 환경과 동일한 H2인데 왜 안되지?&lt;/i&gt; 라고 생각한다면, 당신은 디버깅 구렁텅이에 빠지게 될 것이다....&lt;/b&gt; 이 설정은 &lt;code&gt;EmbeddedDatabaseConnection&lt;/code&gt;에 설정된 기본 H2 url에 랜덤한 유니크 UUID를 데이터베이스 이름으로 자동 생성된 임베디드 인메모리 데이터베이스인데, &lt;b&gt;&lt;code&gt;;MODE=MYSQL;&lt;/code&gt;이 없다!! 초기 어플리케이션 세팅 이후 신경쓰지 않아 까먹었을 텐데, H2와 MySQL은 유사하지만 다른 문법이 존재(특히 DDL)하기 때문에 MySQL 모드로 DataSource를 설정해야만 했다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public enum EmbeddedDatabaseConnection {  

    /**  
     * No Connection.     
     * */    
     NONE(null),  

    /**  
     * H2 Database Connection.     
     * */    
     H2(&quot;jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE&quot;),  

    /**  
     * Derby Database Connection.     
     * */    
     DERBY(&quot;jdbc:derby:memory:%s;create=true&quot;),  

    /**  
     * HSQL Database Connection.     * @since 2.4.0  
     */    
     HSQLDB(&quot;org.hsqldb.jdbcDriver&quot;, &quot;jdbc:hsqldb:mem:%s&quot;);

// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;해결 방안&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2가지 방법으로 해결할 수 있는데&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;spring.jpa.properties.hibernate.dialect&lt;/code&gt; 을 기본 값 혹은 H2Dialect로 설정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AutoConfigureTestDatabase&lt;/code&gt; 의 &lt;code&gt;Replace&lt;/code&gt; 전략을 &lt;code&gt;NONE&lt;/code&gt;으로 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 로컬에서 H2로 쓴 사람이라면 1번을 택하면 되지만, 일반적으로 프로덕션 환경에 맞추기 위해 MySQL 모드로 사용하고 있는 사람도 많을 것이다. 특히 나는 날짜와 관련된 쿼리를 사용했는데, H2와 MySQL에서 지원하는 날짜 함수가 다르기 때문에 1번을 사용할 수 없다. 1번을 사용하면 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-26 15.07.12 1.png&quot; data-origin-width=&quot;2316&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nHAUq/btsGf9Qg3y4/EuhTmqlKH4mn9F0V4avKuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nHAUq/btsGf9Qg3y4/EuhTmqlKH4mn9F0V4avKuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nHAUq/btsGf9Qg3y4/EuhTmqlKH4mn9F0V4avKuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnHAUq%2FbtsGf9Qg3y4%2FEuhTmqlKH4mn9F0V4avKuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2316&quot; height=&quot;594&quot; data-filename=&quot;스크린샷 2024-01-26 15.07.12 1.png&quot; data-origin-width=&quot;2316&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 어노테이션을 통해 Replace 전략을 NONE으로 설정하면, properties에서 설정한 DataSource를 그대로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@DataJpaTest  
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles(&quot;test&quot;)  
class MemberPostRepositoryCustomTest {
// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외명과 Caused by 절을 통해서는 원인을 알 수 없는 문제였다. 근본적인 문제를 추측하고 구글링과 프레임워크 코드를 분석함으로써 해결할 수 있었는데, 이게 진짜 이상적인 디버깅같아서 뿌듯했다. 블로깅에도 꽤 많은 시간이 들었는데, 결국은 기록이 다 남는 것 같으니 열심하 해봐야겠다.&lt;/p&gt;</description>
      <category>   Backend/SpringBoot</category>
      <category>JPA</category>
      <category>springboot</category>
      <category>test</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/66</guid>
      <comments>https://way-code.tistory.com/entry/DataJpaTest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%91-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%B0%8F-%EC%BF%BC%EB%A6%AC-%EC%98%88%EC%99%B8-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0#entry66comment</comments>
      <pubDate>Mon, 1 Apr 2024 22:49:35 +0900</pubDate>
    </item>
    <item>
      <title>Datagrip과 SSH Tunneling을 통해 데이터베이스를 편하고 안전하게 관리하기</title>
      <link>https://way-code.tistory.com/entry/Datagrip%EA%B3%BC-SSH-Tunneling%EC%9D%84-%ED%86%B5%ED%95%B4-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EB%A5%BC-%ED%8E%B8%ED%95%98%EA%B3%A0-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;소마 멘토님과 배포 DB의 데이터를 직접 수정하는 일에 대해 이야기하는 도중에 터미널을 켜는 나를 보고 깜짝 놀라며 터미널로 관리를 하고 있냐고 물었던 것이 아직도 기억난다. 컬럼명 조차도 제대로 보이지 않는 mysql cli를 썼던 이유를 생각해봤더니, 백엔드 개발을 여기저기서 배울 때 이렇게 하는 것만 봐왔고 프로젝트 개발까지만 하고 유지보수 단계까지 가보지 않아 불편함을 크게 느껴보지 못해서 였던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datagrip을 배우면서 신세계를 맛보고 여기저기에 츄라이해왔는데, 불호하는 사람은 못본 것 같다. 하나 아쉬운 점은 public subnet에 있어야만 가능해서 보안적으로 아쉽다는 것이었는데, 내가 모르는 것이였다.... SSH Tunneling을 통해 안전하면서도 편하게 DB를 다룰 수 있게 되어 블로깅해보게 되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;기존 방식의 문제&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;before.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eeGKd7/btsGaY14rqr/a8bQcZVchPgYOJzMtcvtt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eeGKd7/btsGaY14rqr/a8bQcZVchPgYOJzMtcvtt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eeGKd7/btsGaY14rqr/a8bQcZVchPgYOJzMtcvtt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeeGKd7%2FbtsGaY14rqr%2Fa8bQcZVchPgYOJzMtcvtt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;367&quot; data-filename=&quot;before.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 많이 쓰는 구조는 DB도 public subnet에 두고 개방한 포트로 직접 접속하는 것이다. 하지만 엄밀히는 private subnet에 두고 WAS에서만 포트를 통해 접근가능하게 해야하지만, 매번 EC2 터미널에 접속하고 mysql client을 사용하는게 여간 귀찮은게 아니다. 특히, Datagrip은 외부 네트워크인 로컬이기 때문에 DB가 privat subnet에 있다면, 사용할 수 없었다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;해결 방법&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;after.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LPSsg/btsGcttp0ru/IVDIkpcwF2khWY4lCvBEF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LPSsg/btsGcttp0ru/IVDIkpcwF2khWY4lCvBEF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LPSsg/btsGcttp0ru/IVDIkpcwF2khWY4lCvBEF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLPSsg%2FbtsGcttp0ru%2FIVDIkpcwF2khWY4lCvBEF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;253&quot; data-filename=&quot;after.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 SSH Tunneling 기능을 이용하면 DB를 private subnet에 분리하면서도 Datagrip으로 손쉽게 DB에 접속할 수 있다. SSH Tunneling은 말 그대로 SSH 접속이 가능한 내부 서버를 이용해(한 번 거쳐서) 내부 네트워크에서만 접속가능한 DB에 접속하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Datagrip SSH Tunneling&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datagrip을 통해 SSH Tunneling 하는 방법은 간단하다. Data Source를 추가하여 DB 접속 정보를 입력하는데, Host는 내부 IP를 입력해야 한다. 내부 서버를 통해 접속하는 것이므로, 내부 네트워크를 기준으로 작성해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GzrAz/btsGbRH7RBh/WBO66MtNL7XcavM4K5Mfg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GzrAz/btsGbRH7RBh/WBO66MtNL7XcavM4K5Mfg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GzrAz/btsGbRH7RBh/WBO66MtNL7XcavM4K5Mfg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGzrAz%2FbtsGbRH7RBh%2FWBO66MtNL7XcavM4K5Mfg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1826&quot; height=&quot;1577&quot; data-filename=&quot;제목 없는 다이어그램.drawio.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Use SSH tunnel&lt;/code&gt;을 활성화 시키고 SSH configuration을 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio1.png&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;1577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zjs77/btsGce37tNx/WdqTAcI7DpjrQmCBKPQrhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zjs77/btsGce37tNx/WdqTAcI7DpjrQmCBKPQrhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zjs77/btsGce37tNx/WdqTAcI7DpjrQmCBKPQrhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZjs77%2FbtsGce37tNx%2FWdqTAcI7DpjrQmCBKPQrhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1825&quot; height=&quot;1577&quot; data-filename=&quot;제목 없는 다이어그램.drawio1.png&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;1577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이용할 서버의 SSH 접속 정보를 입력한다. 나의 경우 늘 pem key를 이용하는데, key 인증도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio2.png&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;1631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfQmPV/btsF9IljZ89/iKO2SvHKYUkuAqy2ZwaEJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfQmPV/btsF9IljZ89/iKO2SvHKYUkuAqy2ZwaEJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfQmPV/btsF9IljZ89/iKO2SvHKYUkuAqy2ZwaEJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfQmPV%2FbtsF9IljZ89%2FiKO2SvHKYUkuAqy2ZwaEJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1889&quot; height=&quot;1631&quot; data-filename=&quot;제목 없는 다이어그램.drawio2.png&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;1631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이후 &lt;code&gt;Test Connection&lt;/code&gt; 이 다음과 같이 성공되면, 세팅 성공이다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio3.png&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;1578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b49qPo/btsGbEPJNHw/F5UGzilw3Jngh2mMQWMat0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b49qPo/btsGbEPJNHw/F5UGzilw3Jngh2mMQWMat0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b49qPo/btsGbEPJNHw/F5UGzilw3Jngh2mMQWMat0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb49qPo%2FbtsGbEPJNHw%2FF5UGzilw3Jngh2mMQWMat0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1825&quot; height=&quot;1578&quot; data-filename=&quot;제목 없는 다이어그램.drawio3.png&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;1578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>   Backend/Database</category>
      <category>Database</category>
      <category>datagrip</category>
      <category>SSH</category>
      <category>tunneling</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/65</guid>
      <comments>https://way-code.tistory.com/entry/Datagrip%EA%B3%BC-SSH-Tunneling%EC%9D%84-%ED%86%B5%ED%95%B4-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EB%A5%BC-%ED%8E%B8%ED%95%98%EA%B3%A0-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0#entry65comment</comments>
      <pubDate>Fri, 29 Mar 2024 08:42:16 +0900</pubDate>
    </item>
    <item>
      <title>Transaction 이란?</title>
      <link>https://way-code.tistory.com/entry/Transaction-%EC%9D%B4%EB%9E%80</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 노션 블로그의&amp;nbsp;&lt;a href=&quot;https://www.notion.so/sckwon770/Transaction-26a7c41509ea40c180f8b96c1a4ff7dd?pvs=4&quot;&gt;Transaction 이란? (2020.12.22)&lt;/a&gt;로부터 마이그레이션된 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;트랜잭션-transaction-이란&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;트랜잭션, Transaction 이란?&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Transaction이란, DB의 상태를 변화시키기 위해 수행하는 작업의 단위를 뜻한다. DB의 상태를 변화시킨다는 것은 아래의 SQL을 이용해서 DB에 접근하는 것을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT&lt;/li&gt;
&lt;li&gt;INSERT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;UPDATE&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;착각하지 말아야 할 것은 작업의 단위는 SQL 한 문장이 아니다. 많은 SQL 명령문들을 사람이 정하는 기준에 따라 정하는 것이 작업의 단위다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;계시판을 예를들어 보면, 사용자가 게시글을 작성하고, 업로드 버튼을 누른다. 그 후에 다시 게판에 돌아왔을 때, 게시판은 자신의 글을 포함한 업데이트된 게시판을 보게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 상황을 DB 작업으로 옮기면, 사용자가 업로드 버튼을 눌렀을 시, INSERT 문을 사용하여 입력한 게시글의 데이터를 올린다. 그 후에 게시판을 구성할 데이터를 다시 SELECT 하여 최신 정보로 유지한다. 여기서 작업의 단위는 INSERT문과 SELECT 문 둘다를 합친 것이다. 이러한 작업 단위를 하나의 Transaction이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;  설명을 위해 게시물의 업로드와 업데이트를 예를 들었지만, 트랜잭션의 핵심은 **원자성**, 모두 성공 혹은 실패의 특성으로 묶일 수 있는 작업들의 단위로 보면된다. 또한, 각 작업들의 서로 의존적이여야만 하는 것도 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;transaction의-특징&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Transaction의 특징&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Transaction의 특징은 크게 4가지로 구분된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원자성, Atomicity&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Transaction이 DB에 모두 반영되거나, 아니면 전혀 반영되지 않아야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성, Consistency&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Transaction의 작업 터리 결과가 항상 일관성이 있어야 한다. Transaction이 진행되는 동안에 DB가 변경되더라도 처음에 참조한 DB로 진행이 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;독립성, Isolation&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 둘 이상의 Transaction이 동시에 실행되고 있을 경우, 어떤 하나의 Transaction이라도, 다른 Transaction의 연산에 끼어들 수 없다. 수행중인 Transaction이 완료될 때 까지 다른 Transaction의 결과를 참조할 수가 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지속성, Durability&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Transaction이 성공적으로 완료됬을 경우, 결과는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;영구적&lt;/b&gt;으로 반영되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;transaction의-commit-rollback-연산&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Transaction의 Commit, Rollback 연산&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Commit&lt;/b&gt;이란 하나의 Transaction이 성공적으로 끝났고, DB가 일관성있는 상태에 있을 때, 하나의 Transaction이 끝났다는 것을 알려주기 위해 사용하는 연산이다. 이 연산을 사용하면 수행했던 Transaction이 로그에 저장되며, 후에 Rollback 연산을 수행했었던 Transaction단위로 하는 것을 도와준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Rollback&lt;/b&gt;이란 하나의 Transaction 처리가 비정상적으로 종료되어 Transaction의 원자성이 깨진경우, Transaction을 처음부터 다시 시작하거나, Transaction의 부분적으로만 연상된 결과를 다시 취소시킨다. 후에 사용자가 Transaction 처리된 단위대로 Rollback을 진행할 수도 있다.&lt;/p&gt;</description>
      <category>   Backend/SpringBoot</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/64</guid>
      <comments>https://way-code.tistory.com/entry/Transaction-%EC%9D%B4%EB%9E%80#entry64comment</comments>
      <pubDate>Mon, 26 Feb 2024 00:43:24 +0900</pubDate>
    </item>
    <item>
      <title>빌드 관리 도구 Maven과 Gradle 비교하기</title>
      <link>https://way-code.tistory.com/entry/%EB%B9%8C%EB%93%9C-%EA%B4%80%EB%A6%AC-%EB%8F%84%EA%B5%AC-Maven%EA%B3%BC-Gradle-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 노션 블로그의&amp;nbsp;&lt;a href=&quot;https://www.notion.so/sckwon770/Maven-Gradle-5f3935c7454c4e8ba73a16d090814c9e?pvs=4&quot;&gt;빌드 관리 도구 Maven과 Gradle 비교하기 (2021.01.06)&lt;/a&gt;로부터 마이그레이션된 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 id=&quot;빌드-관리-도구란&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;빌드 관리 도구란?&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 생성, 테스트 빌드, 배포 등의 작업을 위한 전용 프로그램&lt;/li&gt;
&lt;li&gt;프로젝트에서 작성한 java 코드와 프로젝트 내에 필요한 각종 xml, properties, jar 파일들을 JVM이나 WAS가 인식할 수 있도록 패키징해주는 빌드 과정 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;빌드 자동화 도구&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;프로젝트에 필요한 외부 라이브러리들의 종류와 버전들, 종속성 정보를 명시하여 자동으로 다운로드하고 관리하는 도구&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;maven&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Maven&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Maven은 Java용 프로젝트 관리도구로 Apache의 Ant 대안으로 만들어 졌다. 빌드 중인 프로젝트, 빌드 순서, 라이브러리 종속성 관계를&lt;span&gt;&amp;nbsp;&lt;/span&gt;pom.xml&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 명시한다. 외부저장소에서 필요한 라이브러리와 플러그인들을 다운로드 한 다음, 로컬시스템의 캐시에 모두 저장한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어 &quot;Spring Boot Data JPA Starter&quot; 모듈이 필요하면 Maven Repository에서 해당 모듈을 검색하여 xml 설정 파일에 추가하여 사용할 수 있다. (Maven용 코드 이외에도 gradle 등 다른 빌드 관리용 코드도 있다.)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://jisooo.tistory.com/entry/Spring-%EB%B9%8C%EB%93%9C-%EA%B4%80%EB%A6%AC-%EB%8F%84%EA%B5%AC-Maven%EA%B3%BC-Gradle-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0&quot;&gt;[Spring] 빌드 관리 도구 Maven과 Gradle 비교하기.&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.2.4.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;gradle&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Gradle&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Apache Maven과 Apache Ant에서 볼 수 있는 개념들을 대안으로서 나온 프로젝트 빌드 관리 툴이다. (완전한 오픈소스) Groovy 언어를 사용한 Domain-specific-langauge를 사용한다. (xml보다 훨씬 간결하다) 꽤 큰 규모로 예상되는 multi-project 빌드를 도울 수 있도록 디자인되었다. 프로젝트의 어느 부분이 업데이트되었는지 알기 때문에, 빌드에 점진적으로 추가할 수 있다. (업데이트가 이미 반영된 빌드의 부분은 더 이상 재실행되지 않아, 빌드 시간이 훨씬 단축될 수 있다)&lt;/p&gt;
&lt;h1 id=&quot;maven-vs-gradle&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Maven VS Gradle&lt;/h1&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 시스템이 빌드에 접근하는 방식에는 몇 가지 근본적인 차이점이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gradle은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;작업 의존성 그래프&lt;/b&gt;를 기반으로 하는 반면 Maven은 고정적이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;선형적인 단계의 모델&lt;/b&gt;을 기반으로 한다고 한다. 성능 측면에서는 둘 다 다중 모듈 빌드를 병렬로 실행할 수 있지만, Gradle은 어떤 task가 업데이트되었고 안되었는지 체크하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Incremental build&lt;/b&gt;를 허용한다. 이미 업데이트된 task에 대해서는 작업이 실행되지 않으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;빌드 시간이 훨씬 단축된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 빌드 설정 규모가 점점 키지면 커질수록, 빌드 시간의 차이도 Maven과 비교하여 꽤 격차가 벌어질 수 있을 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Maven의 경우 멀티 프로젝트에서 특정 설정을 다른 모듈에서 사용하려면 상속을 받아야하지만, Gradle은 설정 주입 방식을 제공한다.&lt;/li&gt;
&lt;li&gt;Gradle은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;concurent에 안전한 캐시&lt;/b&gt;를 허용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 2개 이상의 프로젝트에서 동일한 캐시를 사용할 경우, 서로 overwrite되지 않도록 checksum 기반의 캐시를 허용하고, 캐시를 repository와 동기화시킬 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고도로 사용자 정의된 빌드를 작성하기 위해서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;커스터마이징&lt;/b&gt;이 간편한 Gradle을 사용하는게 훨씬 낫다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;속도나 캐시 사용 안정성에 대하여 당연히 Gradle로 사용하는게 이득이겠다는 생각이 든다. 그럼에도 Gradle을 상용하지 않는 이유는 초반에 Maven의 Scope를 지원하지 않았고 성능면에서도 앞설 것이 없었다. 이러한 점은 현재에 해결되었지만 여전히 익숙함의 문제와 새로운 것을 배우는 비용의 문제, 이로 인해 팀프로젝트에서 사용하기 위해서는 다른 팀원도 배워야한 다는 문제가 발생한다. 구글 트렌드가 그 차이를 여실히 보여주고 있는 듯 하다.&lt;/p&gt;</description>
      <category>   Backend/SpringBoot</category>
      <author>sckwon770</author>
      <guid isPermaLink="true">https://way-code.tistory.com/63</guid>
      <comments>https://way-code.tistory.com/entry/%EB%B9%8C%EB%93%9C-%EA%B4%80%EB%A6%AC-%EB%8F%84%EA%B5%AC-Maven%EA%B3%BC-Gradle-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0#entry63comment</comments>
      <pubDate>Sun, 25 Feb 2024 20:33:59 +0900</pubDate>
    </item>
  </channel>
</rss>