목요일

React useEffect 무한루프 문제 해결하기

수요일

Nginx SSL 인증서 만료 해결법

🔍 검색 키워드: Nginx SSL 인증서 만료, Let's Encrypt 갱신, TLS 에러, HTTPS 연결 실패, 인증서 업데이트

Nginx SSL 인증서 만료 해결법

증상

브라우저에서 HTTPS 사이트 접속 시:

NET::ERR_CERT_AUTHORITY_INVALID
  ERR_SSL_OBSOLETE_VERSION
  The certificate has expired
  SSL_ERROR_HANDSHAKE_FAILURE_ALERT

또는 서버 로그에:

SSL_ERROR_RX_RECORD_TOO_LONG
  no shared ciphers
  Peer rejected a valid certificate
  certificate verify failed (self signed certificate)

cURL로 확인 시:

$ curl -v https://example.com
  * SSL certificate problem: certificate has expired

원인

  1. 인증서 만료: Let's Encrypt 90일 정책, 또는 상용 인증서 만료
  2. 자동 갱신 미작동: certbot/Nginx 플러그인 설정 오류
  3. Nginx 설정 오류: 잘못된 인증서 경로
  4. 시스템 시간 오류: 서버 시계가 실시간과 다름
  5. 인증서 체인 미완성: 중간 인증서(Intermediate) 누락
  6. 포트 443 차단: Let's Encrypt 갱신 포트 막힘
  7. 권한 오류: 인증서 파일 읽기 권한 부족

해결 방법

방법 1: 현재 인증서 상태 확인

# 인증서 만료 기간 확인
  openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
    -noout -dates
    
    # 출력 예:
    # notBefore=Jun  4 12:00:00 2024 GMT
    # notAfter=Sep  2 12:00:00 2024 GMT
    
    # SSL 프로토콜 버전 확인
    openssl s_client -connect example.com:443 -tls1_2
    
    # 인증서 체인 확인
    openssl s_client -connect example.com:443 -showcerts
    
    # 남은 기간 확인 (일 수)
    ssl-cert-check -c /etc/letsencrypt/live/example.com/cert.pem
    
    # Nginx 설정에서 인증서 경로 확인
    grep "ssl_certificate" /etc/nginx/sites-enabled/default

방법 2: Let's Encrypt 인증서 자동 갱신

# 1. certbot 설치 (미설치 시)
  sudo apt-get install certbot python3-certbot-nginx
  
  # 2. 인증서 수동 갱신
  sudo certbot renew
  
  # 3. 특정 도메인만 갱신
  sudo certbot renew --cert-name example.com
  
  # 4. 강제 갱신 (만료 60일 전이 아니라도)
  sudo certbot renew --force-renewal
  
  # 5. 갱신 후 Nginx 재로드
  sudo systemctl reload nginx
  
  # 6. Nginx 설정 테스트
  sudo nginx -t

자동 갱신 설정 (cron 또는 systemd):

# crontab 설정 (매일 오전 3시 확인)
  sudo crontab -e
  # 다음 라인 추가:
  0 3 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx
  
  # 또는 systemd timer (권장)
  sudo systemctl list-timers certbot
  sudo systemctl status certbot.timer

방법 3: Nginx 설정 확인 및 수정

server {
      listen 443 ssl http2;
          server_name example.com www.example.com;
          
              # SSL 인증서 경로 확인
                  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
                      ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
                      
                          # TLS 버전 명시 (TLS 1.2 이상)
                              ssl_protocols TLSv1.2 TLSv1.3;
                              
                                  # 최신 암호화 알고리즘
                                      ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
                                          ssl_prefer_server_ciphers on;
                                          
                                              # HSTS 설정 (선택사항)
                                                  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
                                                  
                                                      # 설정 테스트
                                                          # $ sudo nginx -t
                                                              # nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
                                                                  # nginx: configuration file /etc/nginx/nginx.conf test is successful
                                                                  }
                                                                  
                                                                  # HTTP → HTTPS 리다이렉트
                                                                  server {
                                                                      listen 80;
                                                                          server_name example.com www.example.com;
                                                                          
                                                                              location / {
                                                                                      return 301 https://$host$request_uri;
                                                                                          }
                                                                                          }

설정 적용:

sudo nginx -t
  sudo systemctl reload nginx

방법 4: 새 인증서 발급 (기존 만료된 경우)

# 1. certbot 대화형 모드로 새 인증서 발급
  sudo certbot certonly --nginx -d example.com -d www.example.com
  
  # 2. 수동 DNS 검증 방식
  sudo certbot certonly --manual --preferred-challenges dns \
    -d example.com -d www.example.com
    
    # DNS TXT 레코드 추가 후 엔터
    
    # 3. 발급된 인증서 확인
    sudo ls -la /etc/letsencrypt/live/example.com/

방법 5: 시스템 시간 확인 및 수정

# 시스템 시간 확인
  date
  
  # 시간이 틀렸다면 수정
  sudo timedatectl set-ntp true  # NTP 자동 동기화
  sudo timedatectl set-timezone Asia/Seoul
  
  # 수정 확인
  date
  timedatectl
  
  # 시간 동기화 강제 실행
  sudo ntpdate -s ntp.ubuntu.com

방법 6: SSL 점검 및 모니터링

# SSL Labs 온라인 테스트 (브라우저에서)
  # https://www.ssllabs.com/ssltest/analyze.html?d=example.com
  
  # 로컬에서 SSL 테스트
  sudo apt-get install sslscan
  sslscan --no-failed example.com:443
  
  # 인증서 투명성 로그 확인
  curl https://ct.googleapis.com/log/all_logs_list.json
  
  # Nginx 에러 로그 확인
  sudo tail -f /var/log/nginx/error.log
  
  # Certbot 갱신 로그 확인
  sudo tail -f /var/log/letsencrypt/letsencrypt.log

정리표

에러 메시지 원인 해결법
certificate has expired 인증서 만료 certbot renew 실행
SSL_ERROR_HANDSHAKE_FAILURE_ALERT TLS 설정 오류 Nginx SSL 설정 확인
no shared ciphers 암호화 불일치 ssl_ciphers 최신 버전 설정
certificate verify failed 인증서 체인 누락 fullchain.pem 사용 확인
ERR_SSL_OBSOLETE_VERSION 구버전 TLS TLS 1.2 이상으로 설정

: Let's Encrypt는 90일마다 갱신이 필요합니다. certbot renew --dry-run으로 자동 갱신을 사전 테스트하고, 갱신 후 systemctl reload nginx로 무중단 재로드하세요. Nginx 재시작 시 기존 연결은 유지되므로 서비스 중단이 없습니다.

Kubernetes ImagePullBackOff 에러 해결법

🔍 검색 키워드: k8s ImagePullBackOff, 쿠버네티스 이미지 풀 실패, Docker 레지스트리 인증, 이미지 태그 오류, Pod 시작 실패

Kubernetes ImagePullBackOff 에러 해결법

증상

Kubernetes Pod를 배포했을 때 다음과 같은 상태에서 멈춘다:

$ kubectl get pods -n production
  NAME                    READY   STATUS             RESTARTS   AGE
  app-deployment-abc123   0/1     ImagePullBackOff   2          5m

상세 확인 시:

$ kubectl describe pod app-deployment-abc123 -n production
  Events:
    Type     Reason                 Age   Message
      ----     ------                 ----  -------
        Normal   Scheduled              5m    Successfully assigned production/app-deployment-abc123 to worker-node-1
          Normal   BackOff                4m    Back-off pulling image "myrepo/app:v1.0"
            Warning  Failed                 4m    Failed to pull image "myrepo/app:v1.0": rpc error: code = Unknown desc = Error response from daemon: unauthorized
              Warning  Failed                 3m    Back-off pulling image

또는 다음과 같은 메시지:

image not found
  image pull rate limit exceeded
  no such image
  invalid reference format

원인

  1. 이미지명 오류: 잘못된 저장소명, 태그 또는 레지스트리 주소
  2. 인증 실패: Private Docker 레지스트리 접근 권한 없음
  3. 네트워크 단절: 워커 노드에서 레지스트리 접근 불가
  4. 레지스트리 다운: Docker Hub, ECR 등 서비스 장애
  5. 이미지 미존재: 푸시되지 않은 이미지 태그
  6. 레이트 제한: Docker Hub 무료 계정 풀 한도 초과
  7. CPU/메모리 부족: 노드 리소스 부족으로 스케줄링 실패

해결 방법

방법 1: 이미지명 및 태그 확인

# 현재 Pod의 이미지 정보 확인
  kubectl get pod app-deployment-abc123 -n production -o yaml | grep image
  
  # 정확한 이미지명 확인 (레지스트리 포함)
  # 형식: [registry]/[repository]/[image]:[tag]
  # 정상 예: docker.io/myrepo/app:v1.0
  # 정상 예: gcr.io/my-project/app:latest
  # 정상 예: ecr.amazonaws.com/123456789.dkr.ecr.us-east-1.amazonaws.com/app:v1.0

Deployment 수정:

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: app-deployment
      namespace: production
      spec:
        replicas: 3
          selector:
              matchLabels:
                    app: app
                      template:
                          metadata:
                                labels:
                                        app: app
                                            spec:
                                                  containers:
                                                        - name: app
                                                                image: docker.io/myrepo/app:v1.0  # 정확한 이미지명
                                                                        imagePullPolicy: IfNotPresent      # 또는 Always

방법 2: Private 레지스트리 인증 설정

# 1. Docker 자격증명으로 Secret 생성
  kubectl create secret docker-registry regcred \
    --docker-server=gcr.io \
      --docker-username=_json_key \
        --docker-password="$(cat ~/gcr-key.json)" \
          --docker-email=user@example.com \
            -n production
            
            # 2. 또는 기존 docker config 파일 사용
            kubectl create secret generic regcred \
              --from-file=.dockerconfigjson=$HOME/.docker/config.json \
                --type=kubernetes.io/dockercfg \
                  -n production

Deployment에서 사용:

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: app-deployment
    spec:
      template:
          spec:
                imagePullSecrets:
                      - name: regcred  # 위에서 생성한 Secret 이름
                            containers:
                                  - name: app
                                          image: gcr.io/my-project/app:v1.0

방법 3: 워커 노드 네트워크 확인

# 워커 노드에서 직접 레지스트리 연결 확인
  kubectl debug node/worker-node-1 -it --image=ubuntu
  # Pod 내에서:
  apt-get update && apt-get install -y curl
  curl -I https://gcr.io
  curl -I https://docker.io
  
  # 또는 임시 Pod에서 테스트
  kubectl run test-curl --image=curlimages/curl -it --rm -- \
    curl -v https://gcr.io

방법 4: 로컬에서 이미지 빌드 및 푸시 확인

# 1. 로컬에서 이미지 빌드
  docker build -t myrepo/app:v1.0 .
  
  # 2. 레지스트리에 푸시
  docker push myrepo/app:v1.0
  
  # 3. 푸시된 이미지 확인
  # Docker Hub: https://hub.docker.com/r/myrepo/app
  # GCR: gcloud container images list
  # ECR: aws ecr describe-images --repository-name app
  
  # 4. 로컬에서 이미지 실행 가능 확인
  docker run --rm myrepo/app:v1.0 --version

방법 5: imagePullPolicy 조정

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: app-deployment
    spec:
      template:
          spec:
                containers:
                      - name: app
                              image: myrepo/app:v1.0
                                      # imagePullPolicy 옵션:
                                              # Always: 매번 레지스트리에서 풀 (기본, 태그 latest 사용 시)
                                                      # IfNotPresent: 로컬에 없을 때만 풀
                                                              # Never: 로컬에서만 사용 (오프라인 환경)
                                                                      imagePullPolicy: Always

방법 6: 디버깅 및 재시도

# Pod 상세 로그 확인
  kubectl logs app-deployment-abc123 -n production --previous
  
  # 이벤트 확인 (시간 역순)
  kubectl get events -n production --sort-by='.lastTimestamp'
  
  # Pod 재생성 (자동 재시도)
  kubectl rollout restart deployment/app-deployment -n production
  
  # 캐시 클리어 후 재배포
  kubectl set image deployment/app-deployment \
    app=myrepo/app:v1.1 \
      -n production
      
      # 또는 현재 이미지로 강제 롤아웃
      kubectl rollout restart deployment/app-deployment -n production

정리표

원인 증상 해결법
이미지명 오류 image not found 정확한 이미지명 확인
인증 실패 unauthorized imagePullSecrets 설정
네트워크 단절 connection timeout 워커 노드 네트워크 확인
레지스트리 장애 service unavailable 레지스트리 상태 확인
태그 미존재 manifest not found docker push 재실행
레이트 제한 rate limit exceeded 인증된 계정으로 전환
노드 리소스 부족 OutOfmemory, OutOfDisk 노드 리소스 확인

: 배포 전에 로컬 환경에서 docker push까지 성공하는지 확인하고, 클러스터에는 imagePullPolicy: Always 설정으로 항상 최신 이미지를 가져오도록 설정하면 버전 관리가 쉬워집니다.