<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>두 손끝의 창조자</title>
    <link>https://codinglog.tistory.com/</link>
    <description>기억 못 할 것 같은 것들을 기록하는 블로그</description>
    <language>ko</language>
    <pubDate>Wed, 24 Jun 2026 09:19:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>codinglog</managingEditor>
    <image>
      <title>두 손끝의 창조자</title>
      <url>https://tistory1.daumcdn.net/tistory/45653/attach/534c6d560ea34e9eb3db809f30e53ebc</url>
      <link>https://codinglog.tistory.com</link>
    </image>
    <item>
      <title>ssh 비밀번호 없이 접속하기. ssh key pair</title>
      <link>https://codinglog.tistory.com/345</link>
      <description>&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t ed25519 -f ~/.ssh/dev_&amp;lt;server&amp;gt; -C &amp;quot;dev&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;passphrase는 Enter로 비워둡니다.&lt;/p&gt;
&lt;p&gt;공개키를 서버에 등록:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-copy-id -i ~/.ssh/dev_&amp;lt;server&amp;gt;.pub dev@&amp;lt;server-ip&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt; 등록:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sshconfig&quot;&gt;Host dev
  HostName &amp;lt;server-ip&amp;gt;
  User dev
  Port 22
  IdentityFile ~/.ssh/dev_&amp;lt;server&amp;gt;
  IdentitiesOnly yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;접속 테스트:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정리하면: &lt;strong&gt;키 생성 → 공개키 서버 등록 → &lt;code&gt;~/.ssh/config&lt;/code&gt;에 Host 등록 → &lt;code&gt;ssh dev&lt;/code&gt; 테스트&lt;/strong&gt; 순서입니다.&lt;/p&gt;</description>
      <category>OS</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/345</guid>
      <comments>https://codinglog.tistory.com/345#entry345comment</comments>
      <pubDate>Mon, 18 May 2026 08:22:00 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes 클러스터 접속 불가 트러블슈팅: connection refused에서 인증서 만료까지</title>
      <link>https://codinglog.tistory.com/344</link>
      <description>&lt;h2&gt;문제 발생&lt;/h2&gt;
&lt;p&gt;작년까지 잘 사용하던 Kubernetes 클러스터에 &lt;code&gt;kubectl get pod&lt;/code&gt; 명령을 실행했더니 갑자기 아래와 같은 오류가 발생했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;E0113 11:36:45.788189   43303 memcache.go:265] &amp;quot;Unhandled Error&amp;quot; err=&amp;quot;couldn&amp;#39;t get current server API group list: Get \&amp;quot;https://10.90.3.131:6443/api?timeout=32s\&amp;quot;: dial tcp 10.90.3.131:6443: connect: connection refused&amp;quot;
The connection to the server 10.90.3.131:6443 was refused - did you specify the right host or port?&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;트러블슈팅 과정&lt;/h2&gt;
&lt;h3&gt;1단계: 문제 범위 좁히기&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;connection refused&lt;/code&gt; 에러는 API Server 자체에 TCP 연결조차 되지 않는다는 의미다. 인증이나 권한 문제 이전에 네트워크나 서버 상태에 문제가 있다는 것이다.&lt;/p&gt;
&lt;p&gt;일단 클러스터 서버에 SSH로 직접 접속을 시도했고, 접속은 정상적으로 되었다. 서버는 살아있다는 뜻이다.&lt;/p&gt;
&lt;h3&gt;2단계: 클러스터 서버에서 직접 테스트&lt;/h3&gt;
&lt;p&gt;클러스터 서버 내부에서도 &lt;code&gt;kubectl get pod&lt;/code&gt; 명령을 실행해봤다. 역시나 동일한 오류가 발생했다. 이제 원인을 두 가지로 좁힐 수 있었다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;6443 포트가 막혔거나&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Server 인증서가 만료되었거나&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;작년까지 잘 사용했다는 점, 그리고 정확히 1년이 지난 시점이라는 점을 고려하면 인증서 만료 가능성이 높았다.&lt;/p&gt;
&lt;h3&gt;3단계: 클러스터 인증서 갱신&lt;/h3&gt;
&lt;p&gt;클러스터의 인증서를 갱신하기로 결정했다. kubeadm 기반 클러스터라면 다음 명령으로 인증서를 갱신할 수 있다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo kubeadm certs renew all
sudo systemctl restart kubelet&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;인증서 갱신 후 다시 로컬에서 접속을 시도했다.&lt;/p&gt;
&lt;h3&gt;4단계: 새로운 에러 발생&lt;/h3&gt;
&lt;p&gt;이번에는 다른 에러가 나타났다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;E0113 13:29:47.493890   59567 memcache.go:265] &amp;quot;Unhandled Error&amp;quot; err=&amp;quot;couldn&amp;#39;t get current server API group list: the server has asked for the client to provide credentials&amp;quot;
error: You must be logged in to the server (the server has asked for the client to provide credentials)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;connection refused&lt;/code&gt;에서 &lt;code&gt;provide credentials&lt;/code&gt;로 에러가 바뀌었다. 이는 문제가 한 단계 진전되었다는 의미다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이전&lt;/strong&gt;: API Server 자체에 접속 불가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;현재&lt;/strong&gt;: API Server 접속은 되지만 인증 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;직감적으로 알 수 있었다. 클러스터의 인증서는 갱신했지만, &lt;strong&gt;로컬의 &lt;code&gt;~/.kube/config&lt;/code&gt; 파일은 여전히 만료된 인증서를 사용하고 있었던 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;5단계: 로컬 kubeconfig 갱신&lt;/h3&gt;
&lt;p&gt;클러스터 서버에서 새로 생성된 &lt;code&gt;admin.conf&lt;/code&gt;를 로컬로 복사했다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 클러스터 서버에서
sudo cat /etc/kubernetes/admin.conf

# 로컬에서
# 위 내용을 ~/.kube/config에 덮어쓰기&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 다시 &lt;code&gt;kubectl get pod&lt;/code&gt;를 실행했다. &lt;strong&gt;접속 성공!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;문제의 근본 원인&lt;/h2&gt;
&lt;p&gt;이번 장애의 핵심은 &lt;strong&gt;인증서 만료&lt;/strong&gt;였다. kubeadm으로 구성한 Kubernetes 클러스터는 기본적으로 인증서 유효기간이 &lt;strong&gt;1년&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;트러블슈팅 과정을 정리하면:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클러스터 서버의 API Server 인증서 만료&lt;/strong&gt; → &lt;code&gt;connection refused&lt;/code&gt; 발생&lt;/li&gt;
&lt;li&gt;클러스터 인증서 갱신 → API Server 정상화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;로컬 kubeconfig의 클라이언트 인증서 만료&lt;/strong&gt; → &lt;code&gt;provide credentials&lt;/code&gt; 발생&lt;/li&gt;
&lt;li&gt;로컬 kubeconfig 갱신 → 정상 접속&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;재발 방지 방법&lt;/h2&gt;
&lt;h3&gt;1. 인증서 만료일 확인&lt;/h3&gt;
&lt;p&gt;정기적으로 인증서 만료일을 확인하자:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubeadm certs check-expiration&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 인증서 갱신 루틴 설정&lt;/h3&gt;
&lt;p&gt;운영 캘린더에 &lt;strong&gt;연 1회 인증서 갱신 작업&lt;/strong&gt;을 등록해두자. 만료 전에 미리 갱신하면 장애를 예방할 수 있다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo kubeadm certs renew all
sudo systemctl restart kubelet

# 로컬 kubeconfig도 업데이트
sudo cp /etc/kubernetes/admin.conf ~/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. kubeconfig 백업&lt;/h3&gt;
&lt;p&gt;중요한 설정은 항상 백업해두자:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp ~/.kube/config ~/.kube/config.bak.$(date +%F)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;교훈&lt;/h2&gt;
&lt;p&gt;이번 트러블슈팅을 통해 얻은 교훈:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;에러 메시지를 정확히 읽자&lt;/strong&gt;: &lt;code&gt;connection refused&lt;/code&gt;와 &lt;code&gt;provide credentials&lt;/code&gt;는 완전히 다른 문제다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제를 단계별로 좁혀가자&lt;/strong&gt;: 네트워크 → 서버 → 인증 순으로 체계적으로 접근&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정기 점검이 중요하다&lt;/strong&gt;: 인증서 만료는 장애가 아니라 &amp;quot;정기 점검 누락&amp;quot;에 가깝다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kubeadm 기반 클러스터를 운영한다면, 인증서 관리는 필수다. 1년에 한 번, 꼭 인증서를 갱신하자!&lt;/p&gt;</description>
      <category>Kubernates</category>
      <category>certs</category>
      <category>kubeadm</category>
      <category>kubectl</category>
      <category>Kubernetes</category>
      <category>인증서</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/344</guid>
      <comments>https://codinglog.tistory.com/344#entry344comment</comments>
      <pubDate>Tue, 13 Jan 2026 14:05:48 +0900</pubDate>
    </item>
    <item>
      <title>[Remind] DNS 레코드 핵심 개념 정리: A부터 TXT까지</title>
      <link>https://codinglog.tistory.com/343</link>
      <description>&lt;p&gt;프로젝트를 새로 시작하거나 서버를 이전할 때 반드시 마주하게 되는 것이 DNS 설정입니다. 하지만 한 번 설정해두면 한동안 건드릴 일이 없다 보니, 막상 설정 페이지를 열면 각 레코드의 차이가 헷갈리곤 합니다.&lt;/p&gt;
&lt;p&gt;오늘은 DNS의 기본 동작 원리부터 실무에서 놓치기 쉬운 심화 레코드까지, 다시 한번 머릿속에 구조를 잡아보고자 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1. DNS: 인터넷의 전화번호부&lt;/h3&gt;
&lt;p&gt;DNS(Domain Name System)는 &lt;code&gt;example.com&lt;/code&gt;과 같은 인간 친화적인 도메인 이름을 컴퓨터가 이해할 수 있는 IP 주소(&lt;code&gt;192.0.2.1&lt;/code&gt;)로 변환해주는 시스템입니다. DNS 레코드는 이 전화번호부에 적힌 개별 항목들이라고 이해하면 쉽습니다.&lt;/p&gt;
&lt;h3&gt;2. 기본 레코드 타입 요약&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;레코드 타입&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th&gt;핵심 특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;도메인을 IPv4 주소로 매핑&lt;/td&gt;
&lt;td&gt;가장 기본적인 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AAAA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;도메인을 IPv6 주소로 매핑&lt;/td&gt;
&lt;td&gt;차세대 주소 체계 (모바일 대응 필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CNAME&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;도메인 별칭(Alias) 생성&lt;/td&gt;
&lt;td&gt;IP가 아닌 다른 도메인을 가리킴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;권한 있는 네임서버 지정&lt;/td&gt;
&lt;td&gt;도메인 관리를 위임할 서버 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h3&gt;3. A 레코드와 AAAA 레코드 (Address)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A 레코드&lt;/strong&gt;는 도메인을 특정 서버의 IPv4 주소에 직접 연결합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;example.com.    A    192.0.2.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;최근에는 &lt;strong&gt;AAAA 레코드&lt;/strong&gt;의 중요성이 커지고 있습니다. 모바일 환경이나 아시아권 네트워크에서는 IPv6 사용 비중이 매우 높기 때문입니다. 호환성을 위해 A와 AAAA 레코드를 함께 운영하는 &amp;#39;Dual Stack&amp;#39; 설정을 권장합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;4. CNAME과 그 한계, 그리고 ALIAS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CNAME(Canonical Name)&lt;/strong&gt;은 특정 호스트 이름을 다른 도메인으로 리다이렉션할 때 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장점:&lt;/strong&gt; 서버 IP가 바뀌어도 연결된 도메인(A 레코드)만 수정하면 CNAME은 자동으로 따라갑니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제약:&lt;/strong&gt; RFC 표준상 루트 도메인(예: &lt;code&gt;example.com&lt;/code&gt;)에는 CNAME을 사용할 수 없습니다. NS나 SOA 레코드와 공존할 수 없기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;[Solution] ALIAS / ANAME 레코드&lt;/strong&gt;&lt;br&gt;최근 AWS Route 53이나 Cloudflare 같은 서비스에서는 이 제약을 해결하기 위해 &lt;strong&gt;ALIAS&lt;/strong&gt; 혹은 &lt;strong&gt;CNAME Flattening&lt;/strong&gt; 기능을 제공합니다. 루트 도메인에서도 CNAME처럼 동작하게 해주므로, 클라우드 환경에서는 적극 활용하는 것이 좋습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;5. 실무 필수: MX와 TXT 레코드&lt;/h3&gt;
&lt;p&gt;웹 서비스 외에 이메일이나 보안 설정을 위해 반드시 알아야 하는 레코드입니다.&lt;/p&gt;
&lt;h4&gt;MX (Mail Exchanger)&lt;/h4&gt;
&lt;p&gt;해당 도메인의 이메일을 수신할 서버를 지정합니다. 우선순위(Priority) 값을 함께 설정하며, 숫자가 낮을수록 우선권이 높습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;example.com.    MX    10    aspmx.l.google.com.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;TXT (Text)&lt;/h4&gt;
&lt;p&gt;도메인에 대한 텍스트 정보를 담습니다. 주로 &lt;strong&gt;소유권 인증&lt;/strong&gt;이나 &lt;strong&gt;이메일 보안(SPF, DKIM, DMARC)&lt;/strong&gt; 설정에 사용됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &amp;quot;우리 서버 외에 다른 곳에서 보낸 메일은 스팸이야&amp;quot;라고 선언하는 SPF 설정은 이제 필수입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;6. 운영의 묘미: TTL (Time To Live)&lt;/h3&gt;
&lt;p&gt;TTL은 DNS 레코드 정보가 캐시 서버에 머무는 시간을 초(second) 단위로 설정한 값입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;평상시:&lt;/strong&gt; 3600(1시간) 이상으로 설정하여 DNS 쿼리 부하를 줄입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버 이전 시:&lt;/strong&gt; 작업 24시간 전에 TTL을 300(5분) 정도로 낮춰두어야 합니다. 그래야 IP 변경 시 전 세계 사용자들이 빠르게 변경된 서버로 접속할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;7. DNS 설정 검증하기&lt;/h3&gt;
&lt;p&gt;설정을 마쳤다면 터미널에서 &lt;code&gt;dig&lt;/code&gt; 명령어로 올바르게 반영되었는지 확인해봅니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# A 레코드 확인
dig example.com A

# 전체 경로 추적 (어느 네임서버에서 응답하는지 확인)
dig +trace example.com

# 특정 레코드(MX) 확인
dig example.com MX
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;마치며: DNS는 서비스의 시작점입니다.&lt;/h3&gt;
&lt;p&gt;DNS는 한 번 장애가 발생하면 전파 속도 때문에 복구에 시간이 걸립니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;중복 네임서버(NS)&lt;/strong&gt;를 반드시 확보하고,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;루트 도메인 제약&lt;/strong&gt;을 이해하며,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPv6(AAAA)&lt;/strong&gt;와 &lt;strong&gt;이메일 보안(TXT)&lt;/strong&gt;까지 챙긴다면&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;견고한 인프라의 첫 단추를 잘 꿰었다고 할 수 있습니다. 다음에 새로운 도메인을 설정할 때 이 글이 좋은 리마인드가 되길 바랍니다.&lt;/p&gt;</description>
      <category>보안</category>
      <category>dns</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/343</guid>
      <comments>https://codinglog.tistory.com/343#entry343comment</comments>
      <pubDate>Tue, 13 Jan 2026 11:14:59 +0900</pubDate>
    </item>
    <item>
      <title>Hex-encoded string must have an even number of characters</title>
      <link>https://codinglog.tistory.com/342</link>
      <description>&lt;h1&gt;Spring Security PasswordEncoder Hex 디코딩 오류 해결&lt;/h1&gt;
&lt;h2&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;로그인 시 다음과 같은 오류가 발생했습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java.lang.IllegalArgumentException: Hex-encoded string must have an even number of characters
    at org.springframework.security.crypto.codec.Hex.decode(Hex.java:51)
    at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.decode(Pbkdf2PasswordEncoder.java:221)
    at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.matchesNonNull(Pbkdf2PasswordEncoder.java:212)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;원인 분석&lt;/h2&gt;
&lt;h3&gt;1. 설정 파일의 비밀번호 형식&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;application.yml&lt;/code&gt;에 다음과 같이 &lt;code&gt;{noop}&lt;/code&gt; prefix를 가진 비밀번호가 설정되어 있었습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;idp:
  users:
    - username: &amp;quot;admin&amp;quot;
      password: &amp;quot;{noop}admin&amp;quot;
    - username: &amp;quot;user&amp;quot;
      password: &amp;quot;{noop}user&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. PasswordEncoder 설정&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PasswordEncoderConfig&lt;/code&gt;에서 &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt;만 사용하도록 설정되어 있었습니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
Pbkdf2PasswordEncoder testPasswordEncoder() {
    return new Pbkdf2PasswordEncoder(
            &amp;quot;&amp;quot;,
            SALT_LENGTH,
            ITERATIONS,
            PBKDF2WithHmacSHA256
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 문제점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{noop}&lt;/code&gt; prefix는 &lt;code&gt;NoOpPasswordEncoder&lt;/code&gt;를 의미하는 Spring Security의 표준 형식입니다&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt;는 이 prefix를 인식하지 못하고, &lt;code&gt;{noop}admin&lt;/code&gt; 문자열을 Hex로 디코딩하려고 시도&lt;/li&gt;
&lt;li&gt;결과적으로 &amp;quot;Hex-encoded string must have an even number of characters&amp;quot; 오류 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;해결 방법&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DelegatingPasswordEncoder&lt;/code&gt;를 사용하여 여러 인코더를 지원하도록 수정했습니다.&lt;/p&gt;
&lt;h3&gt;수정된 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package cothe.oidcidp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;

import java.util.Map;

import static org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256;

@Configuration
public class PasswordEncoderConfig {

    public static final int SALT_LENGTH = 16;
    public static final int ITERATIONS = 10;

    @Bean
    public PasswordEncoder passwordEncoder() {
        Pbkdf2PasswordEncoder pbkdf2Encoder = new Pbkdf2PasswordEncoder(
                &amp;quot;&amp;quot;,
                SALT_LENGTH,
                ITERATIONS,
                PBKDF2WithHmacSHA256
        );

        return new DelegatingPasswordEncoder(
                &amp;quot;pbkdf2&amp;quot;,
                Map.of(
                        &amp;quot;noop&amp;quot;, NoOpPasswordEncoder.getInstance(),
                        &amp;quot;pbkdf2&amp;quot;, pbkdf2Encoder
                )
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 변경 사항&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DelegatingPasswordEncoder 도입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 PasswordEncoder를 지원하는 위임 패턴 사용&lt;/li&gt;
&lt;li&gt;비밀번호의 prefix에 따라 적절한 인코더 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;인코더 매핑&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{noop}&lt;/code&gt; → &lt;code&gt;NoOpPasswordEncoder&lt;/code&gt;: 기존 평문 비밀번호 지원&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{pbkdf2}&lt;/code&gt; → &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt;: 새로운 비밀번호는 PBKDF2로 인코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;기본 인코더 설정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;pbkdf2&amp;quot;&lt;/code&gt;를 기본 인코더로 설정하여, prefix가 없는 새 비밀번호는 자동으로 PBKDF2로 인코딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;동작 원리&lt;/h2&gt;
&lt;h3&gt;DelegatingPasswordEncoder의 동작 방식&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;비밀번호 저장 시&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prefix가 없으면 기본 인코더(&lt;code&gt;pbkdf2&lt;/code&gt;) 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{pbkdf2}...&lt;/code&gt; 형식으로 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;비밀번호 검증 시&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{noop}admin&lt;/code&gt; → &lt;code&gt;NoOpPasswordEncoder&lt;/code&gt;로 검증&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{pbkdf2}...&lt;/code&gt; → &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt;로 검증&lt;/li&gt;
&lt;li&gt;prefix가 없으면 기본 인코더로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 기존 비밀번호 (application.yml에서 로드)
password: &amp;quot;{noop}admin&amp;quot;  // NoOpPasswordEncoder로 검증

// 새로 생성되는 비밀번호
passwordEncoder.encode(&amp;quot;newpassword&amp;quot;)  
// → &amp;quot;{pbkdf2}...&amp;quot; 형식으로 저장, Pbkdf2PasswordEncoder로 검증&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;추가 고려사항&lt;/h2&gt;
&lt;h3&gt;NoOpPasswordEncoder Deprecation 경고&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NoOpPasswordEncoder&lt;/code&gt;는 보안상의 이유로 deprecated되었습니다. 하지만 기존 데이터와의 호환성을 위해 유지했습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;향후 마이그레이션 계획:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;기존 &lt;code&gt;{noop}&lt;/code&gt; 비밀번호를 사용자에게 비밀번호 변경 요청&lt;/li&gt;
&lt;li&gt;또는 배치 작업으로 모든 비밀번호를 &lt;code&gt;{pbkdf2}&lt;/code&gt; 형식으로 재인코딩&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;보안 권장사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프로덕션 환경&lt;/strong&gt;에서는 &lt;code&gt;NoOpPasswordEncoder&lt;/code&gt; 사용을 피해야 합니다&lt;/li&gt;
&lt;li&gt;가능한 한 모든 비밀번호를 &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt; 또는 더 강력한 인코더로 마이그레이션&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{noop}&lt;/code&gt; prefix는 개발/테스트 환경에서만 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html&quot;&gt;Spring Security PasswordEncoder 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/password/DelegatingPasswordEncoder.html&quot;&gt;DelegatingPasswordEncoder API 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;요약&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문제&lt;/strong&gt;: &lt;code&gt;Pbkdf2PasswordEncoder&lt;/code&gt;가 &lt;code&gt;{noop}&lt;/code&gt; prefix를 인식하지 못해 Hex 디코딩 오류 발생&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결&lt;/strong&gt;: &lt;code&gt;DelegatingPasswordEncoder&lt;/code&gt;를 사용하여 여러 인코더 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과&lt;/strong&gt;: 기존 &lt;code&gt;{noop}&lt;/code&gt; 비밀번호와 새로운 &lt;code&gt;{pbkdf2}&lt;/code&gt; 비밀번호 모두 정상 처리&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>DelegatingPasswordEncoder</category>
      <category>NoOpPasswordEncoder</category>
      <category>passwordEncoder</category>
      <category>Spring Security</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/342</guid>
      <comments>https://codinglog.tistory.com/342#entry342comment</comments>
      <pubDate>Tue, 13 Jan 2026 10:34:41 +0900</pubDate>
    </item>
    <item>
      <title>Opaque Access Token 이란</title>
      <link>https://codinglog.tistory.com/341</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Opaque Access Token&lt;/b&gt;은 &lt;b&gt;토큰 자체만으로는 의미를 해석할 수 없는(accessible claims가 없는)&lt;/b&gt; 액세스 토큰을 말합니다. 즉, 토큰을 받은 &lt;b&gt;클라이언트나 리소스 서버가 토큰 내부 정보를 직접 읽을 수 없고&lt;/b&gt;, 반드시 &lt;b&gt;인증 서버(Authorization Server)&lt;/b&gt;에 문의해야 유효성이나 권한을 알 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;b&gt;Opaque(불투명)&lt;/b&gt;&lt;br /&gt;토큰 문자열은 단순한 임의 값처럼 보이며, 사용자 정보&amp;middot;권한&amp;middot;만료 시간 같은 &lt;b&gt;클레임이 노출되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 측 검증 필수&lt;/b&gt;&lt;br /&gt;리소스 서버는 토큰을 받으면 &lt;b&gt;토큰 인트로스펙션(Introspection) API&lt;/b&gt; 등을 통해&lt;br /&gt;&amp;ldquo;이 토큰이 유효한가?&amp;rdquo;, &amp;ldquo;어떤 권한이 있는가?&amp;rdquo;를 &lt;b&gt;인증 서버에 질의&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT Access Token과의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분Opaque Access TokenJWT Access Token&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;토큰 내용&lt;/td&gt;
&lt;td&gt;의미 없는 문자열&lt;/td&gt;
&lt;td&gt;JSON 클레임 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클라이언트/리소스 서버&lt;/td&gt;
&lt;td&gt;토큰 해석 불가&lt;/td&gt;
&lt;td&gt;토큰 자체 해석 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 방식&lt;/td&gt;
&lt;td&gt;인증 서버에 매번 확인&lt;/td&gt;
&lt;td&gt;서명 검증으로 자체 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제어/폐기&lt;/td&gt;
&lt;td&gt;&lt;b&gt;즉시 폐기 가능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;폐기 어려움(만료까지 유효)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안 노출&lt;/td&gt;
&lt;td&gt;정보 노출 거의 없음&lt;/td&gt;
&lt;td&gt;클레임 노출 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&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;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;b&gt;엔터프라이즈/금융/사내 시스템&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 흐름 예시&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 로그인&lt;/li&gt;
&lt;li&gt;인증 서버가 &lt;b&gt;Opaque Access Token&lt;/b&gt; 발급&lt;br /&gt;&amp;rarr; 예: X9fA3kLzPq2...&lt;/li&gt;
&lt;li&gt;클라이언트가 API 호출 시 토큰 전달&lt;/li&gt;
&lt;li&gt;리소스 서버가 인증 서버에 토큰 확인 요청&lt;/li&gt;
&lt;li&gt;인증 서버가 유효성&amp;middot;권한 응답&lt;/li&gt;
&lt;li&gt;리소스 서버가 요청 허용/거부&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Opaque Access Token은 &amp;ldquo;서버만 아는 토큰&amp;rdquo;으로, 해석 불가하지만 통제력과 보안성이 높은 액세스 토큰이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안</category>
      <category>Opaquetoken</category>
      <category>Security</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/341</guid>
      <comments>https://codinglog.tistory.com/341#entry341comment</comments>
      <pubDate>Mon, 5 Jan 2026 11:46:33 +0900</pubDate>
    </item>
    <item>
      <title>/var/lib/docker 용량 폭증</title>
      <link>https://codinglog.tistory.com/340</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 개발 서버에서 디스크 사용량 경고가 발생했다. 확인해보니 /var/lib/docker 디렉터리가 무려 &lt;b&gt;359GB&lt;/b&gt;를 사용 중이었다. Docker를 사용하는 서버에서는 흔히 볼 수 있는 문제지만, 이번 사례는 특히 로그 파일 하나가 서버 전체 용량을 압도했던 흥미로운 케이스였기에 기록으로 남긴다.&lt;/p&gt;
&lt;hr data-end=&quot;378&quot; data-start=&quot;375&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;423&quot; data-start=&quot;380&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 발견: /var/lib/docker의 비정상적인 용량 증가&lt;/h2&gt;
&lt;p data-end=&quot;441&quot; data-start=&quot;425&quot; data-ke-size=&quot;size16&quot;&gt;먼저 디스크 용량을 점검했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;df&lt;/span&gt;&lt;/span&gt;&lt;span&gt; -h &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;462&quot; data-ke-size=&quot;size16&quot;&gt;이 명령으로 실제 마운트된 디스크 중 어떤 파티션이 가득 찼는지 확인할 수 있다.&lt;br /&gt;결과적으로 /var/lib/docker가 포함된 파티션이 거의 꽉 찬 상태였다.&lt;/p&gt;
&lt;h3 data-end=&quot;583&quot; data-start=&quot;558&quot; data-ke-size=&quot;size23&quot;&gt;Docker 내부 폴더별 용량 확인&lt;/h3&gt;
&lt;p data-end=&quot;615&quot; data-start=&quot;584&quot; data-ke-size=&quot;size16&quot;&gt;문제를 좁히기 위해 Docker 내부 데이터를 분석했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;du&lt;/span&gt;&lt;/span&gt;&lt;span&gt; -sh /var/lib/docker/* &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;752&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;이 명령을 통해 containers, volumes, overlay2, image 등 어떤 디렉터리가 디스크를 많이 차지하는지 빠르게 파악할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;757&quot; data-start=&quot;754&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;788&quot; data-start=&quot;759&quot; data-ke-size=&quot;size26&quot;&gt;2. 원인 추적: 컨테이너 로그 파일 크기 검사&lt;/h2&gt;
&lt;p data-end=&quot;841&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;특히 Docker 로그(json-file)가 문제일 가능성이 높아 다음 명령을 실행했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;du&lt;/span&gt;&lt;/span&gt;&lt;span&gt; -sh /var/lib/docker/containers/*/*-json.log &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;921&quot; data-start=&quot;908&quot; data-ke-size=&quot;size16&quot;&gt;그리고 예상은 정확했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;348G &lt;/span&gt;&lt;span&gt;&lt;span&gt;/var/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;lib&lt;/span&gt;&lt;span&gt;&lt;span&gt;/docker/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;containers&lt;/span&gt;&lt;span&gt;&lt;span&gt;/.../&lt;/span&gt;&lt;/span&gt;&lt;span&gt;123cb1bf&lt;/span&gt;&lt;span&gt;&lt;span&gt;...-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;json.log 10M &lt;/span&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 48K &lt;/span&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1102&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size16&quot;&gt;무려 &lt;b&gt;348GB짜리 단일 로그 파일&lt;/b&gt;이 발견되었다.&lt;br /&gt;즉, /var/lib/docker 359GB 중 거의 대부분의 원인이 바로 이 로그 파일이었다.&lt;/p&gt;
&lt;hr data-end=&quot;1107&quot; data-start=&quot;1104&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1137&quot; data-start=&quot;1109&quot; data-ke-size=&quot;size26&quot;&gt;3. 추가 확인: 해당 컨테이너는 존재하는가?&lt;/h2&gt;
&lt;p data-end=&quot;1183&quot; data-start=&quot;1139&quot; data-ke-size=&quot;size16&quot;&gt;로그 파일은 존재하지만, 실제 Docker 컨테이너가 남아 있는지 확인해보았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;docker ps -a --no-trunc | grep 123cb1bf &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1320&quot; data-start=&quot;1238&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 목록에는 존재하지 않았다.&lt;br /&gt;즉, **이미 삭제된 컨테이너의 로그 디렉터리만 남아 있는 &amp;lsquo;고아 orphan 로그 파일&amp;rsquo;**이라는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;1344&quot; data-start=&quot;1322&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황은 다음 경우에 흔히 발생한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1465&quot; data-start=&quot;1346&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1388&quot; data-start=&quot;1346&quot;&gt;컨테이너 삭제 후 Docker가 로그 디렉터리를 제대로 정리하지 못함&lt;/li&gt;
&lt;li data-end=&quot;1421&quot; data-start=&quot;1389&quot;&gt;Docker 데몬 비정상 종료 후 파일만 남는 경우&lt;/li&gt;
&lt;li data-end=&quot;1465&quot; data-start=&quot;1422&quot;&gt;오래된 Docker 버전 또는 overlay2 캐시 누적으로 청소 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1470&quot; data-start=&quot;1467&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1508&quot; data-start=&quot;1472&quot; data-ke-size=&quot;size26&quot;&gt;4. 해결: 실행 중인 서비스에 영향 없이 로그 파일 초기화&lt;/h2&gt;
&lt;p data-end=&quot;1571&quot; data-start=&quot;1510&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 이미 존재하지 않았으므로, 용량을 즉시 확보하기 위해 아래 명령으로 로그 파일을 0바이트로 비웠다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;truncate&lt;/span&gt;&lt;/span&gt;&lt;span&gt; -s 0 \ /var/lib/docker/containers/123cb1bf0ba5cdf00ac984d925ef4cb3c3f0ae584808fd7a2ec84c5c82093b73/123cb1bf0ba5cdf00ac984d925ef4cb3c3f0ae584808fd7a2ec84c5c82093b73-json.log &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1849&quot; data-start=&quot;1775&quot; data-ke-size=&quot;size16&quot;&gt;이 명령은 파일을 삭제하는 것이 아니라 &lt;b&gt;내용만 비워서&lt;/b&gt; 용량을 즉시 회수한다.&lt;br /&gt;여기서만 &lt;b&gt;348GB&lt;/b&gt;가 즉시 복구되었다.&lt;/p&gt;
&lt;p data-end=&quot;1876&quot; data-start=&quot;1851&quot; data-ke-size=&quot;size16&quot;&gt;필요하다면 디렉터리 전체를 삭제해도 무방하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;/span&gt;&lt;span&gt; -rf /var/lib/docker/containers/123cb1bf0ba5cdf00ac984d925ef4cb3c3f0ae584808fd7a2ec84c5c82093b73 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1998&quot; data-start=&quot;1995&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2027&quot; data-start=&quot;2000&quot; data-ke-size=&quot;size26&quot;&gt;5. 재발 방지: 로그 로테이션 설정은 필수&lt;/h2&gt;
&lt;p data-end=&quot;2103&quot; data-start=&quot;2029&quot; data-ke-size=&quot;size16&quot;&gt;Docker는 기본적으로 로그를 무제한 저장하는 구조이다.&lt;br /&gt;즉, 로그 로테이션 설정이 없으면 같은 문제가 언제든 재발할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2148&quot; data-start=&quot;2105&quot; data-ke-size=&quot;size16&quot;&gt;/etc/docker/daemon.json을 수정해 로그 크기를 제한한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;log-driver&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;json-file&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;log-opts&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;max-size&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;50m&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;max-file&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;3&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2262&quot; data-start=&quot;2259&quot; data-ke-size=&quot;size16&quot;&gt;적용:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;/span&gt;&lt;span&gt; systemctl restart docker &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2357&quot; data-start=&quot;2307&quot; data-ke-size=&quot;size16&quot;&gt;이제 앞으로 컨테이너 로그 파일은 최대 150MB(50MB &amp;times; 3)까지만 유지된다.&lt;/p&gt;
&lt;hr data-end=&quot;2362&quot; data-start=&quot;2359&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2380&quot; data-start=&quot;2364&quot; data-ke-size=&quot;size26&quot;&gt;6. 마무리: 주요 교훈&lt;/h2&gt;
&lt;p data-end=&quot;2415&quot; data-start=&quot;2382&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 다음을 다시 한 번 명확히 알 수 있었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2605&quot; data-start=&quot;2417&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2474&quot; data-start=&quot;2417&quot;&gt;Docker 환경에서 디스크 부족이 발생하면 가장 먼저 &lt;b&gt;컨테이너 로그 파일&lt;/b&gt;을 확인하자.&lt;/li&gt;
&lt;li data-end=&quot;2525&quot; data-start=&quot;2475&quot;&gt;컨테이너가 이미 삭제되었더라도 &lt;b&gt;고아 로그 파일&lt;/b&gt;은 계속 남아 있을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;2563&quot; data-start=&quot;2526&quot;&gt;기본 설정만 사용하는 Docker는 로그를 무한정 쌓는다.&lt;/li&gt;
&lt;li data-end=&quot;2605&quot; data-start=&quot;2564&quot;&gt;운영 서버라면 반드시 &lt;b&gt;로그 로테이션 설정을 적용&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2758&quot; data-start=&quot;2607&quot; data-ke-size=&quot;size16&quot;&gt;348GB라는 대형 로그 파일 하나로 인해 시스템 장애가 발생할 뻔했지만,&lt;br /&gt;문제를 정확히 파악하고 해결함으로써 빠르게 정상화할 수 있었다.&lt;br /&gt;Docker 기반 운영 환경이라면 누구나 마주칠 수 있는 문제이기에,&lt;br /&gt;비슷한 상황을 겪는 분들에게 도움이 되었으면 한다.&lt;/p&gt;</description>
      <category>Docker</category>
      <category>docker</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/340</guid>
      <comments>https://codinglog.tistory.com/340#entry340comment</comments>
      <pubDate>Tue, 9 Dec 2025 14:42:22 +0900</pubDate>
    </item>
    <item>
      <title>Git으로 빌드 실패 커밋 찾기</title>
      <link>https://codinglog.tistory.com/339</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 갑자기 실패했을 때, 어떤 커밋에서 문제가 생겼는지 찾아야 한다. Git은 이런 상황을 위해 두 가지 효과적인 방법을 제공한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 1: git rebase --exec&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;git rebase [commitid] --exec './gradlew build'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정한 커밋부터 현재까지 모든 커밋을 순차적으로 실행하면서 빌드를 테스트한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;절차&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기준 커밋부터 최신 커밋까지 하나씩 rebase 수행&lt;/li&gt;
&lt;li&gt;각 커밋마다 빌드 실행&lt;/li&gt;
&lt;li&gt;실패 시 git show로 해당 커밋 변경사항 확인&lt;/li&gt;
&lt;li&gt;문제 커밋 파악 후 git rebase --abort로 중단&lt;/li&gt;
&lt;/ol&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;&lt;b&gt;장점&lt;/b&gt;: 정확한 실패 지점 파악 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: 커밋 수가 많으면 시간 소요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 2: git bisect&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;git bisect start
git bisect bad           # 현재 상태(문제 있음)
git bisect good [commitid]  # 정상 동작 커밋
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색으로 문제 커밋을 빠르게 찾는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;절차&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;git bisect start로 시작&lt;/li&gt;
&lt;li&gt;현재 상태를 bad로 지정&lt;/li&gt;
&lt;li&gt;정상 동작하던 과거 커밋을 good으로 지정&lt;/li&gt;
&lt;li&gt;Git이 자동으로 중간 지점 체크아웃&lt;/li&gt;
&lt;li&gt;테스트 후 good 또는 bad 반복&lt;/li&gt;
&lt;li&gt;문제 커밋 자동 도출&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동화&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;git bisect start
git bisect bad
git bisect good v1.0
git bisect run ./gradlew build
&lt;/code&gt;&lt;/pre&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;&lt;b&gt;장점&lt;/b&gt;: 대량 커밋에 대해 빠르고 효율적&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: 빌드 성공/실패 기준이 명확해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 무엇을 사용할까&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커밋 수가 적을 때 (10개 이하)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git rebase --exec 사용&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;h3 data-ke-size=&quot;size23&quot;&gt;커밋 수가 많을 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git bisect 사용&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;h2 data-ke-size=&quot;size26&quot;&gt;실무 적용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CI/CD 파이프라인&lt;/h3&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;# 자동화 스크립트 예시
git bisect run ./scripts/ci-test.sh
&lt;/code&gt;&lt;/pre&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;모노레포 환경에서 git bisect 자동화 도입&lt;/li&gt;
&lt;li&gt;QA 기준이 명확한 프로젝트에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 포인트&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전략적 선택&lt;/b&gt;: 커밋 수에 따라 방법 선택&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화 고려&lt;/b&gt;: CI/CD 환경에서 git bisect run 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확한 기준&lt;/b&gt;: 빌드 성공/실패 판단 기준 설정 필요&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 실패 디버깅은 더 이상 수동으로 하나씩 확인할 필요가 없다. Git의 내장 도구를 활용하면 효율적으로 문제를 해결할 수 있다.&lt;/p&gt;</description>
      <category>git</category>
      <category>gisect</category>
      <category>Rebase</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/339</guid>
      <comments>https://codinglog.tistory.com/339#entry339comment</comments>
      <pubDate>Fri, 11 Jul 2025 08:24:45 +0900</pubDate>
    </item>
    <item>
      <title>SAP Overall Equipment Effectiveness(OEE)</title>
      <link>https://codinglog.tistory.com/338</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;OEE란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OEE는 설비가 얼마나 효율적으로 운영되고 있는지를 종합적으로 평가하는 지표입니다. 이를 이해하기 위해 24시간 운영되는 생산 라인의 실제 사례를 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사례로 보는 OEE 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황:&lt;/b&gt; 24시간 운영 생산 라인, 시간당 1,000개 생산 능력&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;: 24시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계획된 정지 시간&lt;/b&gt;: 2시간 (정기 점검, 교대 시간 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가동 시간&lt;/b&gt;: 22시간 (24시간 - 2시간)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비계획 정지 시간&lt;/b&gt;: 3시간 (설비 고장, 긴급 수리 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순 생산 시간&lt;/b&gt;: 19시간 (22시간 - 3시간)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 끝이 아닙니다. 이론적으로는 19시간 동안 19,000개를 생산해야 하지만, 실제로는 17,000개만 생산되었습니다. 이는 속도 손실로 인한 2시간의 손실을 의미합니다.&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;: 2시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순 운전 시간&lt;/b&gt;: 17시간 (19시간 - 2시간)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;품질 불량&lt;/b&gt;: 1,000개 (1시간 상당)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가치 운전 시간&lt;/b&gt;: 16시간 (17시간 - 1시간)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 24시간 중 실제로 가치를 창출한 시간은 단 16시간에 불과합니다. 계획된 정지 시간 2시간을 제외하더라도 여전히 5시간의 손실이 발생했으며, 이는 분석과 개선이 필요한 영역입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OEE의 핵심 계산 공식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 KPI 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI공식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;순 생산 시간&lt;/td&gt;
&lt;td&gt;가동 시간 - 비계획 정지 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순 운전 시간&lt;/td&gt;
&lt;td&gt;순 생산 시간 - 속도 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가치 운전 시간&lt;/td&gt;
&lt;td&gt;순 운전 시간 - 품질 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;통합 KPI 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KPI계산 공식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;가동률&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;(순 생산 시간 / 가동 시간) &amp;times; 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;성능률&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;(순 운전 시간 / 순 생산 시간) &amp;times; 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;품질률&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;(가치 운전 시간 / 순 운전 시간) &amp;times; 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OEE&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;가동률 &amp;times; 성능률 &amp;times; 품질률 &amp;divide; 10,000&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SAP</category>
      <category>oee</category>
      <category>overall equipment effectiveness</category>
      <category>SAP</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/338</guid>
      <comments>https://codinglog.tistory.com/338#entry338comment</comments>
      <pubDate>Thu, 3 Jul 2025 10:23:49 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security에서 HttpSecurity와 WebSecurity의 차이점</title>
      <link>https://codinglog.tistory.com/337</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 프레임워크에서 중요한 두 가지 보안 구성 요소인 HttpSecurity와 WebSecurity의 차이점에 대해 자세히 알아보겠습니다. 이번 글에서는 &lt;b&gt;Spring Security 6.4.5&lt;/b&gt; 버전을 기준으로 설명하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring Security 6.4.5 개요&lt;/li&gt;
&lt;li&gt;HttpSecurity와 WebSecurity 소개&lt;/li&gt;
&lt;li&gt;HttpSecurity 상세 분석 및 예제&lt;/li&gt;
&lt;li&gt;WebSecurity 상세 분석 및 예제&lt;/li&gt;
&lt;li&gt;두 구성 요소의 주요 차이점&lt;/li&gt;
&lt;li&gt;실제 애플리케이션에서의 활용 방법&lt;/li&gt;
&lt;li&gt;결론&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spring Security 6.4.5 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.x는 이전 버전에 비해 상당한 변화가 있었습니다. 특히 Spring Security 6.4.5는 보안 구성을 위한 최신 패러다임을 도입했으며, 람다 기반 구성과, 개선된 타입 안전성을 제공합니다. 이 버전에서는 WebSecurityConfigurerAdapter가 완전히 제거되었고, 컴포넌트 기반 보안 구성이 표준이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5는 다음과 같은 주요 변경사항을 포함합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HttpSecurity 구성을 위한 람다 DSL 확장&lt;/li&gt;
&lt;li&gt;기본 CSRF 보호 메커니즘 변경&lt;/li&gt;
&lt;li&gt;요청 매처(matcher) 시스템 개선&lt;/li&gt;
&lt;li&gt;@EnableMethodSecurity 의 도입과 기존 @EnableGlobalMethodSecurity의 대체&lt;/li&gt;
&lt;li&gt;AuthorizationManager 기반의 권한 처리&lt;/li&gt;
&lt;li&gt;WebSecurityCustomizer 대신 더 명시적인 보안 구성 접근법&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. HttpSecurity와 WebSecurity 소개&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HttpSecurity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpSecurity는 특정 HTTP 요청에 대한 웹 기반 보안을 구성하는 데 사용됩니다. Spring Security 6.4.5에서는 SecurityFilterChain 빈을 통해 HttpSecurity를 구성합니다. 이는 인증, 권한 부여, 로그인 처리, 로그아웃 처리, CSRF 보호, 세션 관리 등과 같은 HTTP 관련 보안을 담당합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebSecurity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSecurity는 FilterChainProxy를 생성하는 데 사용되며, 웹 보안의 전체적인 설정을 구성합니다. Spring Security 6.4.5에서는 WebSecurity의 사용 방식이 변경되었으며, 대부분의 기능이 HttpSecurity로 통합되었습니다. 특히 정적 리소스 처리 방식이 크게 달라졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. HttpSecurity 상세 분석 및 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서 HttpSecurity는 HTTP 요청에 대한 보안을 구성하는 핵심 클래스로, 다음과 같은 작업을 수행합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인증 메커니즘 구성 (폼 로그인, HTTP 기본 인증, OAuth2 등)&lt;/li&gt;
&lt;li&gt;URL 패턴 기반 접근 제어 설정&lt;/li&gt;
&lt;li&gt;로그인/로그아웃 처리&lt;/li&gt;
&lt;li&gt;CSRF 보호&lt;/li&gt;
&lt;li&gt;세션 관리&lt;/li&gt;
&lt;li&gt;보안 헤더 설정&lt;/li&gt;
&lt;li&gt;코르스(CORS) 구성&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Security 6.4.5의 HttpSecurity 구성 예제&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -&amp;gt; authorize
                .requestMatchers(&quot;/&quot;, &quot;/home&quot;, &quot;/public/**&quot;).permitAll()
                .requestMatchers(&quot;/admin/**&quot;).hasAuthority(&quot;ADMIN&quot;)
                .requestMatchers(&quot;/user/**&quot;).hasAuthority(&quot;USER&quot;)
                .anyRequest().authenticated()
            )
            .formLogin(form -&amp;gt; form
                .loginPage(&quot;/login&quot;)
                .defaultSuccessUrl(&quot;/dashboard&quot;)
                .permitAll()
            )
            .logout(logout -&amp;gt; logout
                .logoutUrl(&quot;/logout&quot;)
                .logoutSuccessUrl(&quot;/login?logout&quot;)
                .deleteCookies(&quot;JSESSIONID&quot;)
                .invalidateHttpSession(true)
                .permitAll()
            )
            .csrf(csrf -&amp;gt; csrf.ignoringRequestMatchers(&quot;/api/**&quot;))  // API 엔드포인트에 대해서만 CSRF 비활성화
            .sessionManagement(session -&amp;gt; session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)
                .expiredUrl(&quot;/login?expired&quot;)
            )
            .headers(headers -&amp;gt; headers
                .frameOptions(frameOptions -&amp;gt; frameOptions.sameOrigin())
                .contentSecurityPolicy(csp -&amp;gt; csp.policyDirectives(&quot;script-src 'self'&quot;))
            );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서의 주요 변경점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hasRole() 대신 hasAuthority() 사용 권장 (더 명확한 의미 전달)&lt;/li&gt;
&lt;li&gt;.csrf().disable() 대신 .csrf(csrf -&amp;gt; csrf.disable()) 사용&lt;/li&gt;
&lt;li&gt;메서드 체이닝 대신 람다 기반 구성 사용&lt;/li&gt;
&lt;li&gt;antMatchers(), mvcMatchers() 등이 통합된 requestMatchers() 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. WebSecurity 상세 분석 및 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서는 WebSecurity 사용 방식이 크게 변경되었습니다. 이전 버전에서 WebSecurityCustomizer를 통해 주로 사용되던 ignoring() 메서드의 기능이 HttpSecurity로 이전되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Security 6.4.5에서의 정적 리소스 처리 방식&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // securityMatcher(): 이 SecurityFilterChain이 적용될 URL 패턴 정의
            // 전체 애플리케이션에 이 필터체인을 적용
            .securityMatcher(&quot;/**&quot;)
            .authorizeHttpRequests(authorize -&amp;gt; authorize
                // requestMatchers(): 특정 URL 패턴에 대한 보안 규칙 정의
                // 정적 리소스에 대한 접근 허용
                .requestMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;, &quot;/css/**&quot;, &quot;/js/**&quot;, &quot;/images/**&quot;, &quot;/webjars/**&quot;, &quot;/favicon.ico&quot;).permitAll()
                .requestMatchers(&quot;/swagger-ui/**&quot;, &quot;/v3/api-docs/**&quot;).permitAll()
                .requestMatchers(&quot;/actuator/health&quot;, &quot;/actuator/info&quot;).permitAll()
                // 나머지 요청에 대한 권한 설정
                .requestMatchers(&quot;/public/**&quot;).permitAll()
                .requestMatchers(&quot;/admin/**&quot;).hasAuthority(&quot;ADMIN&quot;)
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults());
        
        return http.build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서는 정적 리소스 처리를 위해 이전 버전의 WebSecurity.ignoring() 대신, HttpSecurity의 authorizeHttpRequests() 내에서 permitAll()을 사용하여 처리하는 것이 권장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 여전히 특수한 경우에는 아래와 같이 정적 리소스에 대한 보안 필터를 완전히 무시할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -&amp;gt; web
            .ignoring()
            .requestMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;, &quot;/css/**&quot;, &quot;/js/**&quot;, &quot;/images/**&quot;, &quot;/favicon.ico&quot;);
    }
    
    // 다른, 정규 애플리케이션 보안 구성을 위한 SecurityFilterChain 빈...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 Spring Security 팀은 이 방식보다 HttpSecurity 내에서 permitAll()을 사용할 것을 권장합니다. 이는 보안 측면에서 더 명확하고 일관된 접근 방법을 제공하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 두 구성 요소의 주요 차이점 및 securityMatcher vs requestMatcher&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;securityMatcher와 requestMatcher의 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서는 securityMatcher()와 requestMatcher()가 서로 다른 계층과 목적으로 사용됩니다:&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;securityMatcher()&lt;/h4&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;: 특정 SecurityFilterChain 빈이 처리할 URL 패턴을 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적용 수준&lt;/b&gt;: 필터 체인 수준에서 작동하여 어떤 요청이 해당 필터 체인을 통과할지 결정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;우선순위&lt;/b&gt;: 여러 SecurityFilterChain 빈이 있을 때, 가장 먼저 등록된 빈 중에서 securityMatcher가 일치하는 것이 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 사례&lt;/b&gt;: API와 웹 페이지 등 서로 다른 보안 구성이 필요한 요청을 분리할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;requestMatcher()&lt;/h4&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;: 이미 선택된 SecurityFilterChain 내에서 특정 URL 패턴에 대한 구체적인 보안 규칙을 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적용 수준&lt;/b&gt;: authorizeHttpRequests() 메서드 내에서 사용되어 각 URL 패턴에 대한 권한을 지정합니다.&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HttpSecurity와 WebSecurity의 주요 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서 HttpSecurity와 WebSecurity의 주요 차이점은 다음과 같습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특성 HttpSecurity WebSecurity&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;범위&lt;/td&gt;
&lt;td&gt;HTTP 요청에 대한 보안&lt;/td&gt;
&lt;td&gt;전역 웹 보안 (제한적 사용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 기능&lt;/td&gt;
&lt;td&gt;인증, 권한 부여, 로그인/로그아웃, CSRF, 세션 관리&lt;/td&gt;
&lt;td&gt;제한적인 전역 설정, 필터 우회 (권장되지 않음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필터 체인&lt;/td&gt;
&lt;td&gt;보안 필터 체인 구성&lt;/td&gt;
&lt;td&gt;보안 필터 체인 생성 (내부적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 빈도&lt;/td&gt;
&lt;td&gt;높음 (주 구성 방식)&lt;/td&gt;
&lt;td&gt;낮음 (대부분 HttpSecurity로 대체)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 사례&lt;/td&gt;
&lt;td&gt;대부분의 보안 구성&lt;/td&gt;
&lt;td&gt;특수한 경우의 필터 완전 우회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변화 및 권장 사항&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;정적 리소스 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security 6.4.5에서는 WebSecurity.ignoring()보다 HttpSecurity.permitAll()을 사용하는 것을 권장합니다.&lt;/li&gt;
&lt;li&gt;이유: 보안 필터 체인을 통과하되 인증을 요구하지 않는 방식이 필터를 완전히 우회하는 것보다 보안적으로 선호됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필터 순서&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebSecurity는 여전히 HttpSecurity 이전에 평가되지만, 사용 사례가 크게 제한되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구성 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security 6.4.5는 람다 기반 DSL을 통한 구성을 강조합니다.&lt;/li&gt;
&lt;li&gt;메서드 체이닝보다 가독성이 좋고 타입 안전성이 향상된 람다 구성이 권장됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 실제 애플리케이션에서의 활용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서는 다음과 같은 패턴으로 보안을 구성하는 것이 권장됩니다:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@EnableMethodSecurity  // 메서드 수준 보안 활성화 (이전의 @EnableGlobalMethodSecurity 대체)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            // securityMatcher(): 이 SecurityFilterChain 자체가 적용될 URL 패턴 정의
            // 이 필터 체인은 /api/** 경로에만 적용됨
            .securityMatcher(&quot;/api/**&quot;)
            .authorizeHttpRequests(authorize -&amp;gt; authorize
                // requestMatchers(): 이미 securityMatcher로 필터링된 URL 중에서 
                // 특정 패턴에 대한 세부 보안 규칙 정의
                .requestMatchers(&quot;/api/public/**&quot;).permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(session -&amp;gt; session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -&amp;gt; csrf.disable())  // API에 대해 CSRF 비활성화
            .httpBasic(withDefaults())
            .oauth2ResourceServer(oauth2 -&amp;gt; oauth2.jwt(withDefaults()));

        return http.build();
    }
    
    @Bean
    public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            // securityMatcher(&quot;/**&quot;): 모든 URL에 이 필터 체인을 적용
            // 단, 다른 SecurityFilterChain이 먼저 일치하는 URL 패턴은 제외됨
            .securityMatcher(&quot;/**&quot;)
            .authorizeHttpRequests(authorize -&amp;gt; authorize
                // requestMatchers(): securityMatcher 내에서 특정 URL 패턴에 대한 
                // 구체적인 권한 규칙 정의
                // 정적 리소스 처리 - 이전 WebSecurity.ignoring() 대체
                .requestMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;, &quot;/css/**&quot;, &quot;/js/**&quot;, &quot;/images/**&quot;).permitAll()
                // 공개 페이지
                .requestMatchers(&quot;/&quot;, &quot;/home&quot;, &quot;/register&quot;, &quot;/about&quot;).permitAll()
                // 관리자 페이지
                .requestMatchers(&quot;/admin/**&quot;).hasAuthority(&quot;ADMIN&quot;)
                // 나머지 요청
                .anyRequest().authenticated()
            )
            .formLogin(form -&amp;gt; form
                .loginPage(&quot;/login&quot;)
                .permitAll()
            )
            .logout(logout -&amp;gt; logout
                .logoutUrl(&quot;/logout&quot;)
                .logoutSuccessUrl(&quot;/&quot;)
                .permitAll()
                .clearAuthentication(true)
                .invalidateHttpSession(true)
            )
            .rememberMe(remember -&amp;gt; remember
                .key(&quot;uniqueAndSecretKey&quot;)
                .tokenValiditySeconds(86400)
            );

        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean 
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username(&quot;user&quot;)
            .password(passwordEncoder().encode(&quot;password&quot;))
            .authorities(&quot;USER&quot;)
            .build();
            
        UserDetails admin = User.builder()
            .username(&quot;admin&quot;)
            .password(passwordEncoder().encode(&quot;admin&quot;))
            .authorities(&quot;ADMIN&quot;, &quot;USER&quot;)
            .build();
            
        return new InMemoryUserDetailsManager(user, admin);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제는 Spring Security 6.4.5의 주요 특징을 보여줍니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다중 SecurityFilterChain 빈을 통한 경로별 다른 보안 구성&lt;/li&gt;
&lt;li&gt;securityMatcher()를 사용한 필터 체인 자체의 적용 범위 지정&lt;/li&gt;
&lt;li&gt;requestMatchers()를 사용한 SecurityFilterChain 내부에서의 세부적인 URL 보안 규칙 정의&lt;/li&gt;
&lt;li&gt;람다 기반 DSL을 통한 가독성 높은 구성&lt;/li&gt;
&lt;li&gt;정적 리소스 처리를 HttpSecurity 내에서 수행&lt;/li&gt;
&lt;li&gt;@EnableMethodSecurity를 통한 메서드 수준 보안 활성화&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서 HttpSecurity와 WebSecurity의 관계는 이전 버전과 비교하여 크게 변화했습니다:&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;HttpSecurity&lt;/b&gt;는 더욱 중심적인 역할을 하며, 거의 모든 보안 구성이 이를 통해 이루어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WebSecurity&lt;/b&gt;는 사용 범위가 크게 축소되었으며, 대부분의 기능이 HttpSecurity로 이전되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5에서의 주요 권장 사항:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;정적 리소스 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebSecurity.ignoring() 대신 HttpSecurity의 authorizeHttpRequests()와 permitAll()을 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 보안 구성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 SecurityFilterChain 빈과 securityMatcher()를 사용하여 다양한 경로에 대해 서로 다른 보안 구성을 적용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;matcher 메서드 구분&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;securityMatcher()는 SecurityFilterChain이 적용될 전체 URL 범위를 정의할 때 사용하세요.&lt;/li&gt;
&lt;li&gt;requestMatchers()는 SecurityFilterChain 내에서 세부적인 URL별 보안 규칙을 정의할 때 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;람다 기반 구성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드 체이닝 대신 람다 기반 DSL을 사용하여 더 가독성 높고 유지보수하기 쉬운 코드를 작성하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메서드 보안&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@EnableGlobalMethodSecurity 대신 @EnableMethodSecurity를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6.4.5의 이러한 변화는 더 명확하고, 타입에 안전하며, 유지보수하기 쉬운 보안 구성을 작성할 수 있게 해줍니다. 이전 버전의 복잡성과 불명확한 부분들이 많이 개선되었으며, 현대적인 접근 방식을 통해 보안 구성의 품질을 높일 수 있게 되었습니다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Spring</category>
      <category>spring-security</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/337</guid>
      <comments>https://codinglog.tistory.com/337#entry337comment</comments>
      <pubDate>Wed, 30 Apr 2025 18:13:41 +0900</pubDate>
    </item>
    <item>
      <title>Stern을 사용한 Kubernetes 다중 Pod 모니터링 가이드</title>
      <link>https://codinglog.tistory.com/336</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes 환경에서 여러 Pod의 로그를 동시에 모니터링해야 할 때가 자주 있습니다. 특히 마이크로서비스 아키텍처에서는 단일 애플리케이션이 여러 Pod에 분산되어 있어 전체 시스템을 모니터링하기가 쉽지 않습니다. 이런 상황에서 Stern은 매우 강력한 도구가 될 수 있습니다. Stern은 여러 Pod의 로그를 동시에 모니터링할 수 있게 해주며, 색상 구분을 통해 가독성을 높여줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stern 설치 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stern은 다양한 운영 체제에서 쉽게 설치할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;macOS&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install stern
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Linux&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;wget https://github.com/stern/stern/releases/download/v1.25.0/stern_1.25.0_linux_amd64.tar.gz
tar -xvf stern_1.25.0_linux_amd64.tar.gz
sudo mv stern /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Windows&lt;/h3&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;choco install stern
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 GitHub에서 바이너리를 직접 다운로드하여 PATH에 추가할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stern 사용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stern은 기본적으로 정규식을 사용하여 Pod 이름을 매칭합니다. 이를 통해 여러 Pod를 한 번에 모니터링할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 사용법&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 'nginx'라는 이름을 포함한 모든 Pod의 로그를 모니터링하려면:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;stern nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네임스페이스 지정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 네임스페이스의 Pod만 모니터링하려면:&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --namespace &amp;lt;namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 네임스페이스에서 Pod를 모니터링하려면:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --all-namespaces
&lt;/code&gt;&lt;/pre&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;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --container &amp;lt;container-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;색상 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod별로 다른 색상을 사용하려면:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --color always
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고급 옵션&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최대 Pod 수 늘리기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Stern은 모든 매칭되는 Pod의 로그를 표시합니다. 하지만 매우 많은 Pod가 있을 경우, 특정 수의 Pod만 모니터링하고 싶을 수 있습니다. 이때 --max-log-requests 옵션을 사용할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --max-log-requests 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 최대 10개 Pod의 로그만 모니터링합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그 부하 줄이는 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;패턴 필터링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 패턴을 포함하는 로그만 표시하려면:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --include &quot;ERROR|WARN&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 패턴을 제외하려면:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --exclude &quot;DEBUG|INFO&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 조건 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시간 이후의 로그만 표시하려면:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --since 10m
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 최근 10분 동안의 로그만 표시합니다. 지원되는 시간 단위는 s(초), m(분), h(시간), d(일) 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점부터의 로그를 보려면 타임스탬프를 사용할 수도 있습니다:&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --since-time &quot;2023-04-09T10:00:00Z&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타 유용한 옵션&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로그 꼬리(tail) 설정&lt;/h4&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --tail 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Pod의 최근 100줄의 로그만 표시합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;출력 형식 지정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Stern은 각 라인 앞에 Pod와 컨테이너 이름을 추가합니다. 출력 형식을 커스터마이즈하려면:&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --template '{{.PodName}} | {{.Message}}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실시간 로그 확인&lt;/h4&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;stern &amp;lt;pod-name-regex&amp;gt; --follow
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 기본적으로 활성화되어 있으며, 실시간으로 로그를 계속 표시합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stern은 Kubernetes 환경에서 여러 Pod의 로그를 효율적으로 모니터링할 수 있는 강력한 도구입니다. 패턴 필터링, 시간 조건 설정, 최대 Pod 수 제한 등의 옵션을 통해 로그 부하를 줄이고 필요한 정보만 집중적으로 모니터링할 수 있습니다. 특히 마이크로서비스 아키텍처나 대규모 Kubernetes 클러스터에서 디버깅 및 모니터링을 수행할 때 Stern의 다양한 기능은 큰 도움이 될 것입니다.&lt;/p&gt;</description>
      <category>Docker</category>
      <category>stern</category>
      <author>codinglog</author>
      <guid isPermaLink="true">https://codinglog.tistory.com/336</guid>
      <comments>https://codinglog.tistory.com/336#entry336comment</comments>
      <pubDate>Wed, 9 Apr 2025 14:32:13 +0900</pubDate>
    </item>
  </channel>
</rss>