<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>하늘속에서IT</title>
    <link>https://atsky.tistory.com/</link>
    <description>하늘은 파란색이니까 내 삶도 파란색이길 ㅎㅎ</description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 13:03:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sggnology</managingEditor>
    <image>
      <title>하늘속에서IT</title>
      <url>https://tistory1.daumcdn.net/tistory/4680678/attach/20e6cd7934b847b3b1ba2191a47e00fc</url>
      <link>https://atsky.tistory.com</link>
    </image>
    <item>
      <title>[Monitoring] Prometheus 상태 확인과 Linux 서버의 Grafana dashboard 구축(feat. Grafana Dashboards Template)</title>
      <link>https://atsky.tistory.com/84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;figure id=&quot;og_1736860084521&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Monitoring] Prometheus &amp;amp; Grafana 를 사용해 Linux 서버의 상태를 모니터링 해보자(feat. docker compose)&quot; data-og-description=&quot;발단&amp;nbsp;테스트 서버의 상태를 모니터링 할 수 있는 도구를 찾던 도중 예전부터 관심있던 Prometheus 와 Grafana 를 도입하여 상태를 확인하고자 합니다. 추후 재사용 될 수 있으니 docker compose 를 통해 &quot; data-og-host=&quot;atsky.tistory.com&quot; data-og-source-url=&quot;https://atsky.tistory.com/83&quot; data-og-url=&quot;https://atsky.tistory.com/83&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y2Df5/hyX0xFBob4/32LasrKiMyrfeqyyQHo76K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/sewrc/hyX0uhQBZ5/wCDHA06pYIfX4Aj6Ie95p0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://atsky.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://atsky.tistory.com/83&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y2Df5/hyX0xFBob4/32LasrKiMyrfeqyyQHo76K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/sewrc/hyX0uhQBZ5/wCDHA06pYIfX4Aj6Ie95p0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Monitoring] Prometheus &amp;amp; Grafana 를 사용해 Linux 서버의 상태를 모니터링 해보자(feat. docker compose)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;발단&amp;nbsp;테스트 서버의 상태를 모니터링 할 수 있는 도구를 찾던 도중 예전부터 관심있던 Prometheus 와 Grafana 를 도입하여 상태를 확인하고자 합니다. 추후 재사용 될 수 있으니 docker compose 를 통해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;atsky.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이전 글에서 docker compose 를 통해 모니터링 환경을 구성해 보았습니다. 그러나 Prometheus 에서 제공하는 PromQL 을 통해 Grafana 에서 dashbaord 를 구성하는 것은 쉬운일이 아닌데요. 따라서, &lt;b&gt;Grafana 에서 제공하는 Dashboards Template 을 사용하여 대중적인 시각화 UI 를 구축하는 부분을 설명&lt;/b&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;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;&amp;nbsp;Grafana 에서 제공하는 템플릿 페이지를 알아보고 모니터링 환경에서 UI 를 어떻게 구축하면 좋을지 알아볼 것입니다.&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;Grafana Dashboards&lt;/h3&gt;
&lt;figure id=&quot;og_1736860325207&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Grafana dashboards | Grafana Labs&quot; data-og-description=&quot;No results found. Please clear one or more filters.&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/grafana/dashboards/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Grafana dashboards | Grafana Labs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;No results found. Please clear one or more filters.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;Grafana 클라우드에서 기록되고 있는&lt;b&gt; Dashboards 템플릿을 확인할 수 있는 웹사이트&lt;/b&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;우리는 Prometheus 를 통해 데이터를 제공받고 있음으로 해당 페이지 &lt;b&gt;옵션에서 Datasource 를 Prometheus 로 선택하여 검색&lt;/b&gt;하여야 합니다. 그렇지 않으면 Grafana Dashbaord 구성시 &lt;b&gt;의도하지 않은 Datasource 를 추가해야 할 염려&lt;/b&gt;가 생길 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2527&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBUHrp/btsLN8D2RPR/DoNffF6ScYznPYqM831BR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBUHrp/btsLN8D2RPR/DoNffF6ScYznPYqM831BR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBUHrp/btsLN8D2RPR/DoNffF6ScYznPYqM831BR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBUHrp%2FbtsLN8D2RPR%2FDoNffF6ScYznPYqM831BR1%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;2527&quot; height=&quot;352&quot; data-origin-width=&quot;2527&quot; data-origin-height=&quot;352&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;Endpoint 를 확인해보면 promehteus.yml 에서 설정한 URL 과 경로로 메트릭을 제공받고 있습니다.&lt;/li&gt;
&lt;li&gt;State 는 현재 해당 Target 의 상태를 의미하며, UP 이라고 명시되어 있어야 메트릭을 정상적으로 제공받고 있는 상태가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Grafana 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Login&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;docker-compose.yml 에서 설정한 env 하위에 명시한 ID, PW 계정을 사용하여 로그인 할 수 있습니다. &lt;b&gt;기본값은 admin/admin&lt;/b&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;Datasource 추가&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pQIeO/btsLM4CHBAG/DA9qJXqbgUKnyIyHpKpkeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pQIeO/btsLM4CHBAG/DA9qJXqbgUKnyIyHpKpkeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pQIeO/btsLM4CHBAG/DA9qJXqbgUKnyIyHpKpkeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpQIeO%2FbtsLM4CHBAG%2FDA9qJXqbgUKnyIyHpKpkeK%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;1264&quot; height=&quot;398&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;398&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;대시보드에 필요한 메트릭 정보 제공을 위한 연결 정보를 생성하여야 합니다. 사진에 명시된 순서에 맞게 클릭하여 검색한후 목록에서 `Prometheus` 를 선택하여주세요.&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-origin-width=&quot;1256&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKpQrn/btsLMrk6H30/1iPIKsQxwCTDP6r9oxmkbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKpQrn/btsLMrk6H30/1iPIKsQxwCTDP6r9oxmkbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKpQrn/btsLMrk6H30/1iPIKsQxwCTDP6r9oxmkbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKpQrn%2FbtsLMrk6H30%2F1iPIKsQxwCTDP6r9oxmkbK%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;1256&quot; height=&quot;340&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;340&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;우측 상단의 `Add new data source` 를 통해 datasource 생성 페이지로 진입합니다.&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-origin-width=&quot;1261&quot; data-origin-height=&quot;985&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OwAdK/btsLNWjHiAA/mbzVbLLL7Wfo8rK1C2yGPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OwAdK/btsLNWjHiAA/mbzVbLLL7Wfo8rK1C2yGPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OwAdK/btsLNWjHiAA/mbzVbLLL7Wfo8rK1C2yGPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOwAdK%2FbtsLNWjHiAA%2FmbzVbLLL7Wfo8rK1C2yGPK%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;1261&quot; height=&quot;985&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;985&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;Name 은 임의로 설정하시면 됩니다.&lt;/li&gt;
&lt;li&gt;Connection 에 http://prometheus:9090 을 입력한뒤, 페이지 스크롤을 아래로 내려 `Save &amp;amp; Test` 버튼을 클릭하여 동작하는지 확인하시면 됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prometheus:9090 와 같이 IP 가 아닌 도메인 호출방식처럼 사용할 수 있는건 docker compose 의 기능중 하나입니다. &lt;b&gt;docker comopse 로 실행된 각 컨테이너&lt;/b&gt;는 그들의 &lt;b&gt;컨테이너명 혹은 서비스명을 통해 각 컨테이너로 접근이 가능&lt;/b&gt;합니다. 따라서,&lt;b&gt; grafana 서비스 내에서 prometheus 로 위와 같은 URL 로 접근&lt;/b&gt;할 수 있게 되는 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Dashboard 추가&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OjqlR/btsLM163aZR/Jpvans1TUs1qXqMKzURYrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OjqlR/btsLM163aZR/Jpvans1TUs1qXqMKzURYrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OjqlR/btsLM163aZR/Jpvans1TUs1qXqMKzURYrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOjqlR%2FbtsLM163aZR%2FJpvans1TUs1qXqMKzURYrK%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;544&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;544&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;Dashboard 페이지로 이동하여 `Import dashboard` 버튼을 통해 진입합니다.&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-origin-width=&quot;1010&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjUu6W/btsLNOsEmIa/mv8LFLr70VDoAgkfgKZwn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjUu6W/btsLNOsEmIa/mv8LFLr70VDoAgkfgKZwn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjUu6W/btsLNOsEmIa/mv8LFLr70VDoAgkfgKZwn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjUu6W%2FbtsLNOsEmIa%2Fmv8LFLr70VDoAgkfgKZwn0%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;1010&quot; height=&quot;1042&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;1042&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;위에서 보는 바와 같이 dashboard 의 특징은 &lt;b&gt;Json 으로 기록되고 저장&lt;/b&gt;됩니다. 그리고, &lt;b&gt;Grafana 는 이를 URL 혹은 Json 파일로 Import 를 가능&lt;/b&gt;케 합니다. 우리는 linux server 에 대한 모니터링을 할 것 이고 URL 로 진행할 것 임으로임으로 아래 URL 을 입력창에 입력한뒤 `Load` 버튼을 눌러주세요.&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;https://grafana.com/grafana/dashboards/1860-node-exporter-full/&lt;/blockquote&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-origin-width=&quot;1256&quot; data-origin-height=&quot;1125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rTlMI/btsLOFuJkV3/zkH6mY9KJJCowy5NNx9qik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rTlMI/btsLOFuJkV3/zkH6mY9KJJCowy5NNx9qik/img.png&quot; data-alt=&quot;마지막입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rTlMI/btsLOFuJkV3/zkH6mY9KJJCowy5NNx9qik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrTlMI%2FbtsLOFuJkV3%2FzkH6mY9KJJCowy5NNx9qik%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;1256&quot; height=&quot;1125&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;1125&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마지막입니다.&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;Load 버튼을 누르게 되면 위와 같은 화면으로 진입하게 됩니다.&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;Name 은 임의로 설정하시면 됩니다.&lt;/li&gt;
&lt;li&gt;아래 Prometheus SelectBox 에서 기존에 추가하였던 Datasource 를 선택해주시면 됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 하는 과정에서 두개의 datasource 를 추가하여 datasource 가 두개 존재하는 것으로 보입니다. 예제를 따라하시는 분들은 이미지와 무관하게, 첫번째 prometheus datasource 를 선택하시면 될겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;style5&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;&amp;nbsp;이상으로 Prometheus 와 Grafna 환경이 준비된 곳에서 어떻게 Dashboard 를 추가할 수 있는지 확인하였습니다. 진행하시면서 원하시는대로 동작하지 않는 부분이 있으면 질문 부탁드립니다. 최대한 빨리 답변 드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ETC/Monitoring</category>
      <category>dashbaords</category>
      <category>Grafana</category>
      <category>monitoring</category>
      <category>Prometheus</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/84</guid>
      <comments>https://atsky.tistory.com/84#entry84comment</comments>
      <pubDate>Tue, 14 Jan 2025 22:37:17 +0900</pubDate>
    </item>
    <item>
      <title>[Monitoring] Docker 와 Prometheus, Grafana 사용해 Linux 서버의 상태 모니터링(feat. docker compose)</title>
      <link>https://atsky.tistory.com/83</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;테스트 서버의 상태를 모니터링 할 수 있는 도구를 찾던 도중 예전부터 관심있던 Prometheus 와 Grafana 를 도입하여 상태를 확인하고자 합니다. 추후 재사용 될 수 있으니 docker compose 를 통해 사용성을 높일 것입니다.&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;Prometheus&lt;/h3&gt;
&lt;figure id=&quot;og_1736770285401&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - prometheus/prometheus: The Prometheus monitoring system and time series database.&quot; data-og-description=&quot;The Prometheus monitoring system and time series database. - prometheus/prometheus&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/prometheus/prometheus&quot; data-og-url=&quot;https://github.com/prometheus/prometheus&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/siG0X/hyX0wzDt6t/4J5nYexeSzE2fkXra9SNb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cZ2MKS/hyX0lx7CMg/GVE9IgawoUCPOSVkuRLDwk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/prometheus/prometheus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/prometheus/prometheus&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/siG0X/hyX0wzDt6t/4J5nYexeSzE2fkXra9SNb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cZ2MKS/hyX0lx7CMg/GVE9IgawoUCPOSVkuRLDwk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - prometheus/prometheus: The Prometheus monitoring system and time series database.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Prometheus monitoring system and time series database. - prometheus/prometheus&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;프로메테우스는 오픈소스 모니터링 및 경고 도구로 특히 &lt;b&gt;시계열 데이터&lt;/b&gt;(시간 기반의 정보, 예를들어 13:00 요청 갯수 100개, 13:01 요청 갯수 101 개, .. 등 시간에 따른 정보를 기록)를 &lt;b&gt;수집하고 분석하는 데 중점을 두는 시스템&lt;/b&gt;입니다. 다양한 특징이 있고 현시점에 활발하게 사용되는 도구로 여기서는 &lt;b&gt;메트릭&lt;/b&gt;(하드웨어나 소프트웨어에 대한 성능 측정을 위한 측정 수치)&lt;b&gt;을 수집&lt;/b&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;Exporter&lt;/h4&gt;
&lt;figure id=&quot;og_1736772478455&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - prometheus/node_exporter: Exporter for machine metrics&quot; data-og-description=&quot;Exporter for machine metrics. Contribute to prometheus/node_exporter development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/prometheus/node_exporter&quot; data-og-url=&quot;https://github.com/prometheus/node_exporter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cbRKWD/hyX0oVUaOl/c8xaGKWtgmUtCzwMk4meO1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fxmHs/hyX0m4SiO6/AIEpsdNVOGu5040q43GY0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/prometheus/node_exporter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/prometheus/node_exporter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cbRKWD/hyX0oVUaOl/c8xaGKWtgmUtCzwMk4meO1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fxmHs/hyX0m4SiO6/AIEpsdNVOGu5040q43GY0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - prometheus/node_exporter: Exporter for machine metrics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Exporter for machine metrics. Contribute to prometheus/node_exporter development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;프로메테우스는 데이터 수집을 위해 Exporter 를 사용합니다. Exporter 는 프로메테우스가&lt;b&gt; 메트릭 수집을 위해 사용하는 도구&lt;/b&gt;로 &lt;b&gt;메트릭 정보를 os or application 으로 부터 수집하여 제공하는 역할&lt;/b&gt;을 합니다. 여기서는 프로메테우스 에서 공식적으로 제공하고 있는 node 로 구현된 exporter 를 사용하여 메트릭을 수집할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 글에서는 예제를 위주로 다루고자 하여 프로메테우스와 Exporter 의 자세한 설명은 공식 문서를 참고하여주세요.)&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;Grafana&lt;/h3&gt;
&lt;figure id=&quot;og_1736772138995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - grafana/grafana: The open and composable observability and data visualization platform. Visualize metrics, logs, and tr&quot; data-og-description=&quot;The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many mo...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/grafana/grafana&quot; data-og-url=&quot;https://github.com/grafana/grafana&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/obS5Y/hyX0zXrieO/D1K6NmmVqw35fKg7kLNWYK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/n4vyL/hyX0yxtrv3/99JOu15e7uWFsmPcJMAioK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://github.com/grafana/grafana&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/grafana/grafana&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/obS5Y/hyX0zXrieO/D1K6NmmVqw35fKg7kLNWYK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/n4vyL/hyX0yxtrv3/99JOu15e7uWFsmPcJMAioK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - grafana/grafana: The open and composable observability and data visualization platform. Visualize metrics, logs, and tr&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many mo...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그라파나는 &lt;b&gt;오픈소스 데이터 시각화 및 모니터링 도구&lt;/b&gt;로, 다양한 데이터 소스를 연결하여 &lt;b&gt;대시보드 형태로 데이터를 시각화하고 분석&lt;/b&gt;할 수 있게 해줍니다. Grafana 는 주로 시계열 데이터를 시각화하고 &lt;b&gt;실시간 모니터링 및 알림 기능을 제공&lt;/b&gt;하는데 사용됩니다. 따라서, 데이터를 수집하여 시계열 정보로 기록하는 Promehteus 와 같이 사용할 것 입니다.&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;style5&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;&amp;nbsp;docker compose 를 사용하여 모니터링을 위한 3개의 컨테이너를 같이 구성하여 사용성을 높이고자 합니다.(윈도우즈 wsl2 로 테스트는 host os 가 windows 임으로 실제 linux 서버에서 테스트해야 동작합니다.)&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;pre id=&quot;code_1736859261403&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
└── tutorial-linux-monitoring-prometheus-grafana/
    ├── prometheus/
    │   └── prometheus.yml
    └── docker-compose.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;prometheus/prometheus.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1736859327512&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global:
  scrape_interval: 20s # 데이터 수집 주기

scrape_configs:
  - job_name: 'linux_server_info'
    # metrics_path: '/metrics' # 메트릭을 수집할 경로를 커스텀 할 수 있음
    static_configs:
      - targets: ['host.docker.internal:9100'] # 메트릭을 제공받을 서버의 주소&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;job_name: prometheus 에서 target check 에 명시될 대상 이름입니다.&lt;/li&gt;
&lt;li&gt;metrics_path: static_configs.targets 에 명시한 속성의 path 입니다.(ex, 위의 경우 host.docker.internal:9100/metrics 로 설정됩니다.)&lt;/li&gt;
&lt;li&gt;static_configs.targets: 메트릭 정보를 수집할 대상 URL 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker-compose.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1736859160247&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volumes:
  prometheus-data: # 시계열 데이터 저장을 위해 사용됩니다.
  grafana-data: # 대시보드 정보 기록을 위해 사용됩니다.

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot; # 호스트 네트워크에 접근하기 위한 설정
    volumes:
      - prometheus-data:/prometheus
      - ./prometheus:/etc/prometheus # prometheus.yml 설정 파일
    command:
      - &quot;--config.file=/etc/prometheus/prometheus.yml&quot;
      - &quot;--web.enable-lifecycle&quot; # Prometheus 서버를 재시작하지 않고도 설정을 다시 읽을 수 있게 함
    ports:
      - &quot;9090:9090&quot;
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin # grafana 사용자 계정
      - GF_SECURITY_ADMIN_PASSWORD=admin # grafana 사용자 비밀번호
    ports:
      - &quot;3000:3000&quot;
    restart: unless-stopped
    volumes:
      - grafana-data:/var/lib/grafana

  linux_server_info:
    image: quay.io/prometheus/node-exporter:latest
    container_name: linux_server_info
    command:
      - '--path.rootfs=/host'
    network_mode: host
    pid: host # 컨테이너에서 호스트의 프로세스를 볼 수 있게 함
    restart: unless-stopped
    depends_on:
      - prometheus
    volumes:
      - '/:/host:ro,rslave' # ro: 읽기전용, rslave: 마운트한 물리 경로의 것을 동기화만 하고 역동기화 하지 않음&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;services.prometheus.command.&quot;--web.enable-lifecycle&quot;: prometheus.yml 을 수정한 뒤 재시작 없이 설정 파일을 reload 할 수 있는 API 를 호출할 수 있게 해줍니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prometheus 컨테이너에 접속하여 `localhost:9090/-/reload` URL 을 POST 형식으로 요청하면 reload 됩니다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행&lt;/h4&gt;
&lt;pre id=&quot;code_1736859761066&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker comopse up -d&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;d 옵션은 백그라운에서 docker compose 가 실행될 수 있게 합니다.&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1736859818573&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대시보드 UI 구축&lt;/h3&gt;
&lt;figure id=&quot;og_1736861869529&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Monitoring] Prometheus 상태 확인과 Linux 서버의 Grafana dashboard 구축(feat. Grafana Dashboards Template)&quot; data-og-description=&quot;발단&amp;nbsp;[Monitoring] Prometheus &amp;amp; Grafana 를 사용해 Linux 서버의 상태를 모니터링 해보자(feat. docker compose)발단&amp;nbsp;테스트 서버의 상태를 모니터링 할 수 있는 도구를 찾던 도중 예전부터 관심있던 Prometheus 와&quot; data-og-host=&quot;atsky.tistory.com&quot; data-og-source-url=&quot;https://atsky.tistory.com/84&quot; data-og-url=&quot;https://atsky.tistory.com/84&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cn6HI9/hyX0oWcv0t/2R4SEzaEratQc5yBx3qa10/img.png?width=800&amp;amp;height=624&amp;amp;face=0_0_800_624,https://scrap.kakaocdn.net/dn/OR4L6/hyX0lkTysR/k2Lp1wcB46dfsMqEuFRj5K/img.png?width=800&amp;amp;height=624&amp;amp;face=0_0_800_624,https://scrap.kakaocdn.net/dn/beNSwR/hyX0ou9f8a/oTAbwMGJc2s11xEIslQiRk/img.png?width=1256&amp;amp;height=1125&amp;amp;face=0_0_1256_1125&quot;&gt;&lt;a href=&quot;https://atsky.tistory.com/84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://atsky.tistory.com/84&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cn6HI9/hyX0oWcv0t/2R4SEzaEratQc5yBx3qa10/img.png?width=800&amp;amp;height=624&amp;amp;face=0_0_800_624,https://scrap.kakaocdn.net/dn/OR4L6/hyX0lkTysR/k2Lp1wcB46dfsMqEuFRj5K/img.png?width=800&amp;amp;height=624&amp;amp;face=0_0_800_624,https://scrap.kakaocdn.net/dn/beNSwR/hyX0ou9f8a/oTAbwMGJc2s11xEIslQiRk/img.png?width=1256&amp;amp;height=1125&amp;amp;face=0_0_1256_1125');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Monitoring] Prometheus 상태 확인과 Linux 서버의 Grafana dashboard 구축(feat. Grafana Dashboards Template)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;발단&amp;nbsp;[Monitoring] Prometheus &amp;amp; Grafana 를 사용해 Linux 서버의 상태를 모니터링 해보자(feat. docker compose)발단&amp;nbsp;테스트 서버의 상태를 모니터링 할 수 있는 도구를 찾던 도중 예전부터 관심있던 Prometheus 와&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;atsky.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;해당 글에서 환경 구축에 따른 대시보드 UI 구축을 설명하고 있습니다.&lt;/p&gt;</description>
      <category>ETC/Monitoring</category>
      <category>docker</category>
      <category>docker compose</category>
      <category>Grafana</category>
      <category>linux server</category>
      <category>node exporter</category>
      <category>Prometheus</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/83</guid>
      <comments>https://atsky.tistory.com/83#entry83comment</comments>
      <pubDate>Tue, 14 Jan 2025 22:04:49 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Assert 가 동작하지 않는다?(feat. Kotlin)</title>
      <link>https://atsky.tistory.com/82</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;assert 를 통한 로직 검증에서 조건이 틀렸음에도 검증되지 않고 Pass-Through 한 경우가 있어 정리합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Assert 란 무엇인가?&lt;/h3&gt;
&lt;pre id=&quot;code_1732242555282&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var a = 1
var b = 2

assert(1 &amp;lt; a+b) // _Assertions object 를 통해 참조된 assert 를 사용
assert(a+b &amp;lt; 1) { &quot;msg&quot; }&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;/li&gt;
&lt;li&gt;코드에서는 assert 로 묶은 조건이 옳음을 주장하다 정도로 볼 수 있을 거 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;_Assertions object&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1732242671465&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName(&quot;PreconditionsKt&quot;)
package kotlin

@PublishedApi
internal object _Assertions {
    @JvmField
    @PublishedApi
    internal val ENABLED: Boolean = javaClass.desiredAssertionStatus()
}

/**
 * Throws an [AssertionError] if the [value] is false
 * and runtime assertions have been enabled on the JVM using the *-ea* JVM option.
 */
@kotlin.internal.InlineOnly
public inline fun assert(value: Boolean) {
    assert(value) { &quot;Assertion failed&quot; }
}

/**
 * Throws an [AssertionError] calculated by [lazyMessage] if the [value] is false
 * and runtime assertions have been enabled on the JVM using the *-ea* JVM option.
 */
@kotlin.internal.InlineOnly
public inline fun assert(value: Boolean, lazyMessage: () -&amp;gt; Any) {
    if (_Assertions.ENABLED) {
        if (!value) {
            val message = lazyMessage()
            throw AssertionError(message)
        }
    }
}&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;위에서 알 수 있듯 assert 는 함수로 오버로딩을 통해 두가지 사용방식을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 assert 가 동작하지 않았던 걸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assert 는 _Assertions.ENABLED 라는 bool 값에 따라 동작 여부가 결정되고, &lt;b&gt;JVM 은 기본적으로 실행할 때 assert 옵션을 사용한다 명시하지 않기에&lt;/b&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;어떻게 assert 옵션을 사용할 수 있을까?(JVM 실행 변수)&lt;/h4&gt;
&lt;pre id=&quot;code_1732243238870&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// -ea(ENABLE ASSERTION) 를 작성하면 ENABLED 옵션이 세팅됩니다.
java -ea -jar YourApplication.jar

// 특정 클래스에 적용
java -ea:com.example.MyClass -jar YourApplication.jar

// 특정 패키지에 적용(`...`를 붙여야 합니다.)
java -ea:com.example... -jar YourApplication.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵션과 무관한 처리(Kotlin)&lt;/h3&gt;
&lt;pre id=&quot;code_1732245420491&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun customAssert(value: Boolean, lazyMessage: () -&amp;gt; String) {
    if (!value) {
        throw AssertionError(lazyMessage())
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring Boot</category>
      <category>assert</category>
      <category>ea</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/82</guid>
      <comments>https://atsky.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 22 Nov 2024 12:18:02 +0900</pubDate>
    </item>
    <item>
      <title>[Baekjoon] 2667번 단지번호붙이기(feat. Java)</title>
      <link>https://atsky.tistory.com/81</link>
      <description>&lt;h2 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;단지에 대한 접근은 DFS 로 처리&lt;/li&gt;
&lt;li&gt;단지구분은 숫자로 하여 DFS 가 접근한 단지에 대해 숫자로 기록&lt;/li&gt;
&lt;li&gt;DFS 가 끝나면 단지 탐색이 끝나게 된 것임으로 단지 숫자 증가&lt;/li&gt;
&lt;/ul&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;style5&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;pre id=&quot;code_1731661108633&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    static int N;
    static int[][] arr;
    static int[][] visited;
    static int beaconValue = 1; // 단지 번호

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());

        arr = new int[N+1][N+1];
        visited = new int[N+1][N+1];

        for(int i=1;i&amp;lt;=N;i++){
            String[] arr1 = br.readLine().split(&quot;&quot;);

            for(int j=1;j&amp;lt;=N;j++){
                arr[i][j] = Integer.parseInt(arr1[j-1]);
            }
        }

        for(int y=1;y&amp;lt;=N;y++){
            for(int x=1;x&amp;lt;=N;x++){
                if(arr[y][x] == 1 &amp;amp;&amp;amp; visited[y][x] == 0) {
                    dfs(y, x);
                    beaconValue += 1; // 단지 번호 증가
                }
            }
        }

        // 단지 수
        int danziCount = beaconValue - 1;

        int[] result = new int[danziCount];

        for(int y=1;y&amp;lt;=N;y++){
            for(int x=1;x&amp;lt;=N;x++){
                int danziValue = visited[y][x];

                if(0 &amp;lt; danziValue)
                    result[danziValue-1] += 1; // 단지별 집 수
            }
        }

        Arrays.sort(result);

        System.out.println(danziCount);

        for (int j : result) {
            System.out.println(j);
        }
    }

    public static void dfs(int Y, int X){
        if(arr[Y][X] == 0)
            return;

        visited[Y][X] = beaconValue;

        if(0 &amp;lt; X - 1 &amp;amp;&amp;amp; arr[Y][X-1] == 1 &amp;amp;&amp;amp; visited[Y][X-1] == 0){
            dfs(Y, X-1);
        }

        if(X + 1 &amp;lt;= N &amp;amp;&amp;amp; arr[Y][X+1] == 1 &amp;amp;&amp;amp; visited[Y][X+1] == 0){
            dfs(Y, X+1);
        }

        if(0 &amp;lt; Y - 1 &amp;amp;&amp;amp; arr[Y-1][X] == 1 &amp;amp;&amp;amp;visited[Y-1][X] == 0){
            dfs(Y-1, X);
        }

        if(Y + 1 &amp;lt;= N &amp;amp;&amp;amp; arr[Y+1][X] == 1 &amp;amp;&amp;amp;visited[Y+1][X] == 0){
            dfs(Y+1, X);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>baejoon</category>
      <category>백준 2667번</category>
      <category>백준 2667번 java</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/81</guid>
      <comments>https://atsky.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 15 Nov 2024 17:59:16 +0900</pubDate>
    </item>
    <item>
      <title>[Baekjoon] 2606번 바이러스(feat. Java)</title>
      <link>https://atsky.tistory.com/80</link>
      <description>&lt;h2 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;li&gt;&lt;b&gt;방문여부&lt;/b&gt;와 &lt;b&gt;접점간에 연결여부&lt;/b&gt;를 통해 &lt;b&gt;접근가능한지 확인&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;style5&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;pre id=&quot;code_1731656112141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    static int[][] arr1;
    static int[][] visited1;
    static HashSet&amp;lt;Integer&amp;gt; set;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        int M = Integer.parseInt(br.readLine());

        arr1 = new int[N+1][N+1];
        visited1 = new int[N+1][N+1];
        set = new HashSet&amp;lt;&amp;gt;();

        for(int i=0;i&amp;lt;M;i++){
            StringTokenizer st = new StringTokenizer(br.readLine());

            int X = Integer.parseInt(st.nextToken());
            int Y = Integer.parseInt(st.nextToken());

            arr1[X][Y] = arr1[Y][X] = 1;
        }

        dfs(1, N);

        System.out.println(set.size() - 1);
    }

    public static void dfs(int V, int N){
        int[] arr = arr1[V];

        set.add(V);

        for(int i=1;i&amp;lt;=N;i++){
            if(i == V)
                continue;

            if(arr[i] == 1 &amp;amp;&amp;amp; visited1[V][i] == 0){
                visited1[V][i] = visited1[i][V] = 1;
                dfs(i, N);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>Baekjoon</category>
      <category>백준</category>
      <category>백준 2606</category>
      <category>백준 2606번 java</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/80</guid>
      <comments>https://atsky.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 15 Nov 2024 16:35:45 +0900</pubDate>
    </item>
    <item>
      <title>[Baekjoon] 1260번 DFS와 BFS(feat. Java)</title>
      <link>https://atsky.tistory.com/79</link>
      <description>&lt;h2 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;DFS 는 재귀호출&lt;/li&gt;
&lt;li&gt;BFS 는 Queue 가 핵심&lt;/li&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;/li&gt;
&lt;li&gt;인덱스 접근의 편의를 위해 0번째 자리는 패딩설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;style5&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;pre id=&quot;code_1731593600737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    public static String dfs = &quot;&quot;;
    public static int[][] arr1;
    public static int[][] visited1;
    public static List&amp;lt;Integer&amp;gt; visitedElement1;

    public static String bfs = &quot;&quot;;
    public static int[][] arr2;
    public static int[][] visited2;
    public static List&amp;lt;Integer&amp;gt; visitedElement2;

    public static void main(String args[]) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st1 = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st1.nextToken());
        int M = Integer.parseInt(st1.nextToken());
        int V = Integer.parseInt(st1.nextToken());

        arr1 = new int[N+1][N+1];
        visited1 = new int[N+1][N+1];
        visitedElement1 = new ArrayList&amp;lt;&amp;gt;();

        arr2 = new int[N+1][N+1];
        visited2 = new int[N+1][N+1];
        visitedElement2 = new ArrayList&amp;lt;&amp;gt;();

        for(int i=0;i&amp;lt;M;i++){
            StringTokenizer st2 = new StringTokenizer(br.readLine());

            int A = Integer.parseInt(st2.nextToken());
            int B = Integer.parseInt(st2.nextToken());

            arr1[A][B] = 1;
            arr1[B][A] = 1;
            arr2[A][B] = 1;
            arr2[B][A] = 1;
        }

        dfs(N, V);
        bfs(V);

        System.out.println(dfs.trim());
        System.out.print(bfs.trim());
    }

    public static void dfs(int N, int V){

        dfs += V+&quot; &quot;;

        visitedElement1.add(V);

        int[] arr = arr1[V];
        int[] visited = visited1[V];

        for(int i=1;i&amp;lt;=N;i++){
            if(i == V)
                continue;

            if(arr[i] == 1 &amp;amp;&amp;amp; visited[i] == 0 &amp;amp;&amp;amp; !visitedElement1.contains(i)){
                visited1[V][i] = 1;
                visited1[i][V] = 1;
                dfs(N, i);
            }
        }
    }

    public static void bfs(int V) {
        Queue&amp;lt;Integer&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
        queue.add(V);
        visitedElement2.add(V);

        while(!queue.isEmpty()){
            int v = queue.poll();
            bfs += v+&quot; &quot;;

            for(int i=1;i&amp;lt;arr2[v].length;i++){
                if(arr2[v][i] == 1 &amp;amp;&amp;amp; visited2[v][i] == 0 &amp;amp;&amp;amp; !visitedElement2.contains(i)){
                    queue.add(i);
                    visited2[v][i] = 1;
                    visited2[i][v] = 1;
                    visitedElement2.add(i);
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>DFS와 BFS</category>
      <category>백준</category>
      <category>백준 1260</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/79</guid>
      <comments>https://atsky.tistory.com/79#entry79comment</comments>
      <pubDate>Thu, 14 Nov 2024 23:14:04 +0900</pubDate>
    </item>
    <item>
      <title>[Baekjoon] 2178번 미로 탐색(feat. Java)</title>
      <link>https://atsky.tistory.com/78</link>
      <description>&lt;h2 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;편의를 위해 N+1, M+1 크기 만큼의 이중배열 사용&lt;/li&gt;
&lt;li&gt;방문 여부 확인을 위한 visited 이중 배열 사용&lt;/li&gt;
&lt;li&gt;bfs 방식을 위한 count 이중 배열 사용(미로 이중배열과 같은)&lt;/li&gt;
&lt;/ul&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;style5&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BFS&lt;/h3&gt;
&lt;pre id=&quot;code_1730902982173&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    static int N;
    static int M;
    static int[][] miro;
    static int[][] countMiro;
    static int[][] visited;
    static Integer result = null;
    static Queue&amp;lt;int[]&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        miro = new int[N+1][M+1];
        countMiro = new int[N+1][M+1];
        visited = new int[N+1][M+1];
        visited[1][1] = 1;

        for(int i=1;i&amp;lt;=N;i++){

            String[] elements = br.readLine().split(&quot;&quot;);

            for(int j=1;j&amp;lt;=M;j++){

                int element = Integer.parseInt(elements[j-1]);

                miro[i][j] = element;
                countMiro[i][j] = element;
            }
        }

        queue.add(new int[]{1, 1});

        bfs();

        System.out.println(result);
    }

    public static void bfs(){

        while(queue.isEmpty() == false){
            int[] current = queue.poll();
            int currentX = current[0];
            int currentY = current[1];

            if(currentX==M &amp;amp;&amp;amp; currentY==N){
                result = countMiro[currentY][currentX];
                return;
            }

            if(0 &amp;lt; currentX-1 &amp;amp;&amp;amp; miro[currentY][currentX-1] == 1 &amp;amp;&amp;amp; visited[currentY][currentX-1] == 0){
                visited[currentY][currentX-1] = 1;
                countMiro[currentY][currentX-1] = countMiro[currentY][currentX]+1;
                queue.add(new int[]{currentX-1, currentY});
            }

            if(0 &amp;lt; currentY-1 &amp;amp;&amp;amp; miro[currentY-1][currentX] == 1 &amp;amp;&amp;amp; visited[currentY-1][currentX] == 0){
                visited[currentY-1][currentX] = 1;
                countMiro[currentY-1][currentX] = countMiro[currentY][currentX]+1;
                queue.add(new int[]{currentX, currentY-1});
            }

            if(currentX+1 &amp;lt;= M &amp;amp;&amp;amp; miro[currentY][currentX+1] == 1 &amp;amp;&amp;amp; visited[currentY][currentX+1] == 0){
                visited[currentY][currentX+1] = 1;
                countMiro[currentY][currentX+1] = countMiro[currentY][currentX]+1;
                queue.add(new int[]{currentX+1, currentY});
            }

            if(currentY+1 &amp;lt;= N &amp;amp;&amp;amp; miro[currentY+1][currentX] == 1 &amp;amp;&amp;amp; visited[currentY+1][currentX] == 0){
                visited[currentY+1][currentX] = 1;
                countMiro[currentY+1][currentX] = countMiro[currentY][currentX]+1;
                queue.add(new int[]{currentX, currentY+1});
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DFS(시간 초과)&lt;/h3&gt;
&lt;pre id=&quot;code_1730903111864&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    static int N;
    static int M;
    static int[][] miro;
    static Integer result = null;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        miro = new int[N+1][M+1];
        int[][] visited = new int[N+1][M+1];
        visited[1][1] = 1;

        for(int i=1;i&amp;lt;=N;i++){

            String[] elements = br.readLine().split(&quot;&quot;);

            for(int j=1;j&amp;lt;=M;j++){

                int element = Integer.parseInt(elements[j-1]);

                miro[i][j] = element;
            }
        }

        dfs(1, 1, visited, 1);

        System.out.println(result);
    }

    public static void dfs(int X, int Y, int[][] visited, int count){

        if(X==M &amp;amp;&amp;amp; Y==N){
            if(result == null){
                result = count;
            }
            else{
                result = Math.min(result, count);
            }
            return;
        }

        if(0 &amp;lt; X-1 &amp;amp;&amp;amp; miro[Y][X-1] == 1 &amp;amp;&amp;amp; visited[Y][X-1] == 0){
            int[][] newVisited = copyVisited(visited);
            newVisited[Y][X-1]=1;
            recurse(X-1, Y, newVisited, count+1);
        }

        if(0 &amp;lt; Y-1 &amp;amp;&amp;amp; miro[Y-1][X] == 1 &amp;amp;&amp;amp; visited[Y-1][X] == 0){
            int[][] newVisited = copyVisited(visited);
            newVisited[Y-1][X]=1;
            recurse(X, Y-1, newVisited, count+1);
        }

        if(X+1 &amp;lt;= M &amp;amp;&amp;amp; miro[Y][X+1] == 1 &amp;amp;&amp;amp; visited[Y][X+1] == 0){
            int[][] newVisited = copyVisited(visited);
            newVisited[Y][X+1]=1;
            recurse(X+1, Y, newVisited, count+1);
        }

        if(Y+1 &amp;lt;= N &amp;amp;&amp;amp; miro[Y+1][X] == 1 &amp;amp;&amp;amp; visited[Y+1][X] == 0){
            int[][] newVisited = copyVisited(visited);
            newVisited[Y+1][X]=1;
            recurse(X, Y+1, newVisited, count+1);
        }
    }

	// 강복사를 통한 visited 물리적 분리, 편의 복사 메서드
    public static int[][] copyVisited(int[][] visited){
        int[][] copied = new int[N+1][M+1];

        for(int i=1;i&amp;lt;=N;i++){
            for(int j=1;j&amp;lt;=M;j++){
                copied[i][j] = visited[i][j];
            }
        }

        return copied;
    }

	// 현재 상황 조회를 위한 로깅 함수
    public static void printMiro(){
        String result = &quot;&quot;;

        System.out.println();
        for(int i=0;i&amp;lt;=N;i++){
            for(int j=0;j&amp;lt;=M;j++){
                result += miro[i][j]+&quot; &quot;;
            }
            result += &quot;\n&quot;;
        }

        System.out.println(result);
    }

	// 현재 상황 조회를 위한 로깅 함수
    public static void printVisited(int[][] visited){
        String result = &quot;&quot;;

        System.out.println();
        for(int i=0;i&amp;lt;=N;i++){
            for(int j=0;j&amp;lt;=M;j++){
                result += visited[i][j]+&quot; &quot;;
            }
            result += &quot;\n&quot;;
        }

        System.out.println(result);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>2178번 java</category>
      <category>java</category>
      <category>백준</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/78</guid>
      <comments>https://atsky.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 6 Nov 2024 23:25:49 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 함수에 인자로 함수를 사용할 수 있다?(feat. 고차함수, 함수형 인터페이스)</title>
      <link>https://atsky.tistory.com/77</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;고차 함수&lt;/h2&gt;
&lt;pre id=&quot;code_1719541699694&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    fun m1(action: () -&amp;gt; Unit) {
        action()
    }&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;고차 함수는&amp;nbsp;&lt;b&gt;함수를 인자로 받거나 함수를 반환하는 함수&lt;/b&gt;&amp;nbsp;입니다. Kotlin 에서는&amp;nbsp;&lt;b&gt;함수 매개변수를 사용&lt;/b&gt;할 수 있습니다.&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1719541853845&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val sum: (Int, Int) -&amp;gt; Int = { a, b -&amp;gt; a + b }
println(sum(3, 4))  // 출력: 7&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;람다는 익명 함수로,&amp;nbsp;&lt;b&gt;코드 블럭을 변수처럼 사용&lt;/b&gt;하거나&amp;nbsp;&lt;b&gt;함수의 인자로 전달&lt;/b&gt;할 수 있게 해줍니다. 람다 표현식은 `{..}` 로 정의됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;`고차 함수`와 `람다 표현식`&lt;/h2&gt;
&lt;pre id=&quot;code_1719542341325&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 고차 함수
fun m1(action: (Int) -&amp;gt; String) {
    println(action(1))
}

// 람다 표현식 호출
m1 {
    &quot;it is $it&quot;
}&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;위와 코드 예제와 같이 Kotlin 에서 고차 함수는 람다 표현식으로 표현될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;함수형 인터페이스 &amp;amp; SAM(Single Abstract Method) 변환&lt;/h2&gt;
&lt;pre id=&quot;code_1719541964610&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수형 인터페이스
interface IDoingThing {
    fun doThing()
}

// 함수형 인터페이스를 인자로 가지는 함수
fun m2(arg1: IDoingThing) {
	arg1.doThing()
}

// 람다 표현식 변환 호출
// IDoingThing 인터페이스의 doThing 을 람다 표현식으로 재정의 한거처럼 표현됩니다.
m2 {
    println(&quot;Doing something&quot;)
}&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;/li&gt;
&lt;li&gt;Kotlin 에서는 위와 같은 `함수형 인터페이스`에 대해 람다 표현식으로 대체할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Dev Language/Kotlin</category>
      <category>kotlin</category>
      <category>Sam</category>
      <category>samc</category>
      <category>single abstract method</category>
      <category>single abstract method conversion</category>
      <category>고차함수</category>
      <category>람다표현식</category>
      <category>함수형 인터페이스</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/77</guid>
      <comments>https://atsky.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 28 Jun 2024 11:42:35 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Security + MockMvc 로 테스트시 주의할 점(feat. kotlin)</title>
      <link>https://atsky.tistory.com/76</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Spring Security 와 MockMvc 를&amp;nbsp; Kotlin 환경에서 검증을 하던 도중 설정(config)이 옳음에도 테스트의 결과가 일관된 반응을 하는 현상을 발견하여 그 이유를 알아보고 왜 그러하였는지 과정을 작성합니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin 고차함수 &amp;amp; 함수형 인터페이스&lt;/h2&gt;
&lt;figure id=&quot;og_1719542602316&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kotlin] 함수에 인자로 함수를 사용할 수 있다?(feat. 고차함수,&quot; data-og-description=&quot;고차 함수 fun m1(action: () -&amp;gt; Unit) { action() }고차 함수는&amp;nbsp;함수를 인자로 받거나 함수를 반환하는 함수&amp;nbsp;입니다. Kotlin 에서는&amp;nbsp;함수 매개변수를 사용할 수 있습니다.&amp;nbsp;람다 표현식val sum: (Int, Int) -&amp;gt; Int &quot; data-og-host=&quot;atsky.tistory.com&quot; data-og-source-url=&quot;https://atsky.tistory.com/77&quot; data-og-url=&quot;https://atsky.tistory.com/77&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VHr6W/hyWrZwwQtd/iJujUgofWEbBYXOkruBKmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fhQGP/hyWrUaTaJH/NAJNcVpEAJDkVMHmHBePj0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://atsky.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://atsky.tistory.com/77&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VHr6W/hyWrZwwQtd/iJujUgofWEbBYXOkruBKmk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fhQGP/hyWrUaTaJH/NAJNcVpEAJDkVMHmHBePj0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Kotlin] 함수에 인자로 함수를 사용할 수 있다?(feat. 고차함수,&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;고차 함수 fun m1(action: () -&amp;gt; Unit) { action() }고차 함수는&amp;nbsp;함수를 인자로 받거나 함수를 반환하는 함수&amp;nbsp;입니다. Kotlin 에서는&amp;nbsp;함수 매개변수를 사용할 수 있습니다.&amp;nbsp;람다 표현식val sum: (Int, Int) -&amp;gt; Int&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;atsky.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kotlin 의 고차함수에 대하여 위 글을 통해 개념을 간단히 보시면 더 이해가 쉽습니다.&lt;/li&gt;
&lt;li&gt;SAM(Single Abstract Method) 변환에 대해서도 다루고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MockMvc 호출을 Kotlin 으로 표현하는 2가지 방식&lt;/h2&gt;
&lt;pre id=&quot;code_1719499379038&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get

..

@Autowired
lateinit var mockMvc: MockMvc

// 정석적인 호출
mockMvc.perform(
	get(&quot;/api/test&quot;)
)..

// SAM 변환을 사용한 호출
mockMvc.perform {
	get(&quot;/api/test&quot;)
    	.buildRequest(it)
}..&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;b&gt;정석적인 호출: &lt;/b&gt;perform 함수의 매개변수인 `RequestBuilder` 를 반환 타입으로 가지는 함수(get, put..)를 호출하여 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SAM 변환 호출: &lt;/b&gt;perform 의 반환타입인 `MockHttpServletRequest` 를 반환하게끔 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2가지 방식을 호출할 가능성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제가 사용하고 있는 &lt;b&gt;Intellij&lt;/b&gt; 의 경우 &lt;b&gt;SAM 변환이 가능한 호출의 경우 SAM 변환 방식을 선 추천&lt;/b&gt;해주고 있습니다.&lt;/li&gt;
&lt;li&gt;따라서, 2가지 방식 어느것이든 &lt;u&gt;같은 결과를 가질 것이라 예측&lt;/u&gt;하고 &lt;u&gt;의심없이 사용&lt;/u&gt;하게 될 수 있다고 생각됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Security 와 사용시 문제 발생&lt;/h2&gt;
&lt;pre id=&quot;code_1719500085252&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;..
@Test
@WithMockUser(roles = [&quot;MANAGER&quot;]) // spring security test 를 위한 annotation 으로 인증된 가상 사용자를 제공합니다.
fun test1() {
	..
    // `/api/test` 엔드포인트는 `MANAGER` 권한이 있어야 접근이 가능합니다.
    mockMvc
    	// 정석적인 호출
    	.perform(get(&quot;/api/test&quot;))
        // SAM 변환 호출
        .perform {
        	get(&quot;/api/test&quot;).buildRequest(it)
        }
    	.andExpect {
        	// 인증된 가상 사용자 권한을 추가하였기에 검증을 통과하여야 합니다.
        	Assertions.assertEquals(it.response.status, 200)
        }
}&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;b&gt;정석적인 호출 사용&lt;/b&gt;시 @WithMockUser 로 제공한 가상 사용자 권한이 허용되어 &lt;b&gt;검증을 통과&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;그러나, &lt;b&gt;SAM 변환 사용&lt;/b&gt;시 사용자 권한 검증에 &lt;b&gt;통과하지 못하게 됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 언급하였던 mockMvc.perform(..) 함수의 파라미터 타입은 `RequestBuilder` 입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그런데 SAM 변환을 적용하면서 저는 RequestBuilder 타입을 반환하는 &lt;b&gt;람다를 인자로 전달&lt;/b&gt;하였습니다.&lt;/li&gt;
&lt;li&gt;이는 ..&lt;b&gt;perform(..) 내부 동작에 문제가 발생&lt;/b&gt;하게 만듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;defaultRequestBuilder 와 Merge 가 되지 않아 TestSecurityContext 미적용&lt;/h4&gt;
&lt;pre id=&quot;code_1719539950572&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MockMvc.perform 메서드 일부
public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
    // requestBuilder 는 Mergeable 인스턴스의 일부인지 묻고 있습니다.
    if (this.defaultRequestBuilder != null &amp;amp;&amp;amp; requestBuilder instanceof Mergeable) {
        requestBuilder = (RequestBuilder)((Mergeable)requestBuilder).merge(this.defaultRequestBuilder);
    }
    ...
}

// (Mergeable)requestBuilder.merge 메서드 일부
public Object merge(@Nullable Object parent) {
    ...
    this.postProcessors.addAll(0, parentBuilder.postProcessors);
    return this;
    ...
}

// TestSecurityContextHolderPostProcessor.postProcessRequeset 메서드 일부
private static final class TestSecurityContextHolderPostProcessor extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor {
    ...
    public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
        ...
        // TestSecurityContext 를 SecurityContext 로 대체
        SecurityContext context = TestSecurityContextHolder.getContext();
        if (!this.EMPTY.equals(context)) {
            this.save(context, request);
        }

        return request;
        ...
    }
}&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;MockMvc.perform 코드블럭의 첫 if 문에 따라 &lt;b&gt;requestBuilder 가 Mergeable 인스턴스에 포함된다면 merge 함수를 통해 defaultRequestBuilder 와 merge&lt;/b&gt; 해주고 있습니다.&lt;/li&gt;
&lt;li&gt;저는 requestBuilder 를 SAM 변환을 통해 람다로 제공하였기에 &lt;b&gt;람다는 Mergeable 인스턴스가 아니라 merge 를 호출할 수 없었습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;merge 메서드 내부 동작을 살펴보면 &lt;b&gt;parentBuilder(defaultRequestBuilder)로 부터 postProcessors 를 merge&lt;/b&gt; 하고 있는 것을 알 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDvP0f/btsIeDPEASH/rmX0xCx9NF5Kt5MHPuNzf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDvP0f/btsIeDPEASH/rmX0xCx9NF5Kt5MHPuNzf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDvP0f/btsIeDPEASH/rmX0xCx9NF5Kt5MHPuNzf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDvP0f%2FbtsIeDPEASH%2FrmX0xCx9NF5Kt5MHPuNzf0%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;975&quot; height=&quot;166&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;parentBuilder.postProcessors 내부에는 SecurityMockMvcRequestPostProcessors 클래스가 포함되어 있습니다.&lt;/li&gt;
&lt;li&gt;이 클래스의 postProcessRequest 메서드는 TestSecurityContext 를 현재 호출의 context 로 대체하여 줍니다.&lt;/li&gt;
&lt;li&gt;따라서, &lt;b&gt;@WithMockUser 로 제공한 정보를 context 에 반영&lt;/b&gt;하게 되어 &lt;b&gt;인증/권한 정보를 가진 채로 호출&lt;/b&gt;할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Builder 패턴혹은 호출전 다양한 설정이 적용되어야 하는 클래스의 경우 설정 적용을 위해 &lt;b&gt;내부적으로 다양한 조건문을 사용&lt;/b&gt;할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이는 인터페이스 혹은 고차함수 등으로 &lt;b&gt;복잡한 인수가 포함&lt;/b&gt;될 수 있습니다.&lt;/li&gt;
&lt;li&gt;따라서, &lt;b&gt;정석적이 방식의 호출을 사용하는 것이 오류 확률을 줄여줄 것&lt;/b&gt;이라 생각합니다.&lt;/li&gt;
&lt;li&gt;위 경우처럼 &lt;u&gt;본인이 구현한 동작이 아니고서야 SAM 변환은 줄이는 것이 좋겠다&lt;/u&gt;는 생각이 듭니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring Boot</category>
      <category>kotlin</category>
      <category>mockmvc.perform</category>
      <category>Spring</category>
      <category>spring security test</category>
      <category>WithMockUser</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/76</guid>
      <comments>https://atsky.tistory.com/76#entry76comment</comments>
      <pubDate>Fri, 28 Jun 2024 10:49:59 +0900</pubDate>
    </item>
    <item>
      <title>[Visual Studio] WCF 수정시 수정/삭제 없이 업데이트 하는 방법(feat. visual studio version)</title>
      <link>https://atsky.tistory.com/74</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;WCF 라이브러리의 수정 이후 참조 추가한 프로젝트에 대해 갱신하려 할 때 삭제/추가 없이 갱신하다가 업데이트 하는 방식이 있을 듯 하여 방법을 정리합니다.&lt;/blockquote&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IDE: Visual Studio 2019&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WCF 업데이트 문서&lt;/h3&gt;
&lt;figure id=&quot;og_1718625890827&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Add, update, or remove WCF data service reference - Visual Studio (Windows)&quot; data-og-description=&quot;Explore how to add, update, or remove a Windows Communication Foundation (WCF) data service reference for .NET Framework applications in Visual Studio.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/en-us/visualstudio/data-tools/how-to-add-update-or-remove-a-wcf-data-service-reference?view=vs-2022#update-a-service-reference&quot; data-og-url=&quot;https://learn.microsoft.com/en-us/visualstudio/data-tools/how-to-add-update-or-remove-a-wcf-data-service-reference?view=vs-2022&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tZ7Wm/hyWldifnXo/icoev0QwBzekz0xkHVTm8k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bhyZf9/hyWleOY1p3/TXnRxOnM8qeUGgnkIFAt9k/img.png?width=1407&amp;amp;height=966&amp;amp;face=0_0_1407_966&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/visualstudio/data-tools/how-to-add-update-or-remove-a-wcf-data-service-reference?view=vs-2022#update-a-service-reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/en-us/visualstudio/data-tools/how-to-add-update-or-remove-a-wcf-data-service-reference?view=vs-2022#update-a-service-reference&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tZ7Wm/hyWldifnXo/icoev0QwBzekz0xkHVTm8k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bhyZf9/hyWleOY1p3/TXnRxOnM8qeUGgnkIFAt9k/img.png?width=1407&amp;amp;height=966&amp;amp;face=0_0_1407_966');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Add, update, or remove WCF data service reference - Visual Studio (Windows)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore how to add, update, or remove a Windows Communication Foundation (WCF) data service reference for .NET Framework applications in Visual Studio.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ms 에서 제공하는 문서에 따르면 update 기능을 제공한다고 하지만 제가 사용중인 2019 버전에서는 제공하지 않는 것으로 보입니다.&lt;/li&gt;
&lt;li&gt;문서의 마지막 작성일자를 보면 비교적 최근이지만 부분적인 수정이 이루어졌을 수 있기에 개발자 커뮤니티를 찾아보았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MS 개발자 커뮤니티 Visual Studio&lt;/h3&gt;
&lt;figure id=&quot;og_1718626022329&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;lt;p&amp;gt;Update service reference menu WCF is not there anymo...&quot; data-og-description=&quot;&amp;lt;p&amp;gt;[regression] [worked-in:16.10] Since a couple of version, it seems the menu to update a service reference is gone:&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;&amp;lt;img src=https://aka.ms/d...&quot; data-og-host=&quot;developercommunity.visualstudio.com&quot; data-og-source-url=&quot;https://developercommunity.visualstudio.com/t/update-service-reference-menu-wcf-is-not-there-any/1552917&quot; data-og-url=&quot;https://developercommunity.visualstudio.com/t/update-service-reference-menu-wcf-is-not-there-any/1552917&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b31rgL/hyWoLRT0ed/Mkk022uKORig1dPwY0mhw1/img.png?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360&quot;&gt;&lt;a href=&quot;https://developercommunity.visualstudio.com/t/update-service-reference-menu-wcf-is-not-there-any/1552917&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developercommunity.visualstudio.com/t/update-service-reference-menu-wcf-is-not-there-any/1552917&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b31rgL/hyWoLRT0ed/Mkk022uKORig1dPwY0mhw1/img.png?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;Update service reference menu WCF is not there anymo...&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;p&amp;gt;[regression] [worked-in:16.10] Since a couple of version, it seems the menu to update a service reference is gone:&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;&amp;lt;img src=https://aka.ms/d...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developercommunity.visualstudio.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문자는 update 기능이 사라져서 곤란하다는 내용입니다.&lt;/li&gt;
&lt;li&gt;이에 답변은 다음과 같이 얘기하고 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;16.11 버전 부터 &lt;b&gt;`연결된 서비스`&lt;/b&gt; 에 접근하여 서비스 옆에 위치한 버튼으로 &lt;b&gt;`수정`&lt;/b&gt;을 선택 &lt;b&gt;마법사를 통해 서비스를 업데이트&lt;/b&gt; 하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1718626313027&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        int TestGet(int value);
    }
    
    public class Service1 : IService1
    {
        public int TestGet(int value)
        {
            return 0;
        }
    }&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;테스트를 위해 WCF 라이브러리에 간단한 메서드(TestGet)를 추가하였습니다.&lt;/li&gt;
&lt;/ul&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnQISf/btsH1D3gKBt/KSKksYlNZCUBoOMVQFj4K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnQISf/btsH1D3gKBt/KSKksYlNZCUBoOMVQFj4K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnQISf/btsH1D3gKBt/KSKksYlNZCUBoOMVQFj4K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnQISf%2FbtsH1D3gKBt%2FKSKksYlNZCUBoOMVQFj4K1%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;732&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;732&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;수정(Edit)을 통한 마법사 호출&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxAdnW/btsH2mzU0ZW/8FZjAg273Fhrigs09jeNSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxAdnW/btsH2mzU0ZW/8FZjAg273Fhrigs09jeNSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxAdnW/btsH2mzU0ZW/8FZjAg273Fhrigs09jeNSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxAdnW%2FbtsH2mzU0ZW%2F8FZjAg273Fhrigs09jeNSk%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;1033&quot; height=&quot;170&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;170&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;마법사를 통한 업데이트&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beSx2b/btsH2EG7nbU/UK1qIK5HU0ZXKxF28Nfmx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beSx2b/btsH2EG7nbU/UK1qIK5HU0ZXKxF28Nfmx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beSx2b/btsH2EG7nbU/UK1qIK5HU0ZXKxF28Nfmx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeSx2b%2FbtsH2EG7nbU%2FUK1qIK5HU0ZXKxF28Nfmx0%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;803&quot; height=&quot;561&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;561&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-origin-width=&quot;779&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7zQq/btsH1ln8DRM/bZ0GrZCKj0bXac0D9gvWaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7zQq/btsH1ln8DRM/bZ0GrZCKj0bXac0D9gvWaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7zQq/btsH1ln8DRM/bZ0GrZCKj0bXac0D9gvWaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7zQq%2FbtsH1ln8DRM%2FbZ0GrZCKj0bXac0D9gvWaK%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;779&quot; height=&quot;188&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마법사에서 이동을 통해 참조하였던 WCF 에 접근하여 현 상태를 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;이상하시게 느낀다면 맞습니다. TestGet 메서드를 추가하였지만 여기서는 노출되지 않고 있습니다. 이를 최신화 하기 위해서는 우선 &lt;b&gt;`중지` 버튼을 통해 현재 상태 추출을 그만&lt;/b&gt;하고 &lt;b&gt;`검색`을 한뒤 최신화된 Service 를 가져오면&lt;/b&gt; 됩니다.&lt;/li&gt;
&lt;li&gt;이 때, Service 정보 즉 &lt;b&gt;WCF&lt;/b&gt; 의 변화가 적용될 수 있게끔 &lt;b&gt;빌드&lt;/b&gt;를 꼭 행해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;`중지` 버튼과 `검색` 버튼을 순차적으로 행한뒤 Service 상태&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQUhh/btsH03uwMRv/R4qekzvsgvzCZaPURMOtd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQUhh/btsH03uwMRv/R4qekzvsgvzCZaPURMOtd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQUhh/btsH03uwMRv/R4qekzvsgvzCZaPURMOtd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQUhh%2FbtsH03uwMRv%2FR4qekzvsgvzCZaPURMOtd1%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;769&quot; height=&quot;127&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이로써 수정/삭제 없이 WCF 를 최신화 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Visual Studio</category>
      <category>visual studio 2019</category>
      <category>WCF</category>
      <category>wcf update</category>
      <category>수정 삭제 없이 최신화</category>
      <author>sggnology</author>
      <guid isPermaLink="true">https://atsky.tistory.com/74</guid>
      <comments>https://atsky.tistory.com/74#entry74comment</comments>
      <pubDate>Mon, 17 Jun 2024 21:22:09 +0900</pubDate>
    </item>
  </channel>
</rss>