도컀 μ»¨ν…Œμ΄λ„ˆ Go μ„œλ²„κ°€ SIGTERM 을 λ¬΄μ‹œν•œ 이유 (feat. PID 1)

2024. 9. 10. 15:41ㆍBackend/🩡 Go

λ°°κ²½

  • Go μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•  λ•Œ νŠΉμ • λ‘œμ§μ„ μˆ˜ν–‰ν•˜κ³ μž ν–ˆμŠ΅λ‹ˆλ‹€.

  • μ‚¬μš©μžκ°€ μˆ˜λ™μœΌλ‘œ (둜컬) μ„œλ²„λ₯Ό μ’…λ£Œν•˜λ©΄ syscall.SIGINT 값이 channel 에 ν• λ‹Ήλ˜λ©΄μ„œ goroutine λ‚΄λΆ€κ°€ μ‹€ν–‰λ˜μ–΄
    둜직 μˆ˜ν–‰ ν›„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ’…λ£Œ(os.Exit(1)) λ©λ‹ˆλ‹€.

  • μ‹€μ œλ‘œ λ‘œμ»¬μ—μ„œ μ„œλ²„λ₯Ό μ‹€ν–‰ν•˜κ³  ctrl + C λͺ…λ Ήμ–΄λ‘œ μ„œλ²„λ₯Ό μ’…λ£Œν•˜λ©΄ goroutine λ‚΄λΆ€ 둜직이 잘 μ‹€ν–‰λμŠ΅λ‹ˆλ‹€.

    func (s *Server) StartServer() error {
    
      s.setServerInfo()
      channel := make(chan os.Signal, 1)
      signal.Notify(channel, syscall.SIGINT) // (1) λ‹¨μˆœ μ’…λ£Œ μ‹œκ·Έλ„
    
      go func() {
          <-channel
        // ... 둜직 μˆ˜ν–‰
          os.Exit(1)
      }()
      return s.engine.Listen(s.port)
    }

도컀 μ»¨ν…Œμ΄λ„ˆν™” 후에도 잘 λ™μž‘ν• κΉŒ?

  • μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ»¨ν…Œμ΄λ„ˆλ‘œ λ§μ•„μ˜¬λ € μ‹€ν–‰ν•˜λ”λΌλ„ μœ„μ˜ 둜직이 잘 λ™μž‘ν• κΉŒμš”? 그렇지 μ•ŠμŠ΅λ‹ˆλ‹€. 싀행쀑인 μ»¨ν…Œμ΄λ„ˆλ₯Ό stop ν•˜λ”λΌλ„ μœ„μ˜ goroutine 은 μˆ˜ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

  • μ΄λŠ” 도컀 μ»¨ν…Œμ΄λ„ˆμ—μ„œ μ‹ ν˜Έ 처리 방식이 둜컬 ν™˜κ²½κ³Ό λ‹€λ₯΄κΈ° λ•Œλ¬ΈμΈλ°,
    λ„μ»€μ—μ„œλŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό stop ν•  λ•Œ, 기본적으둜 SIGTERM μ‹ ν˜Έλ₯Ό λ³΄λƒ…λ‹ˆλ‹€. (κ·Έ 후에 일정 μ‹œκ°„ λ™μ•ˆ μ»¨ν…Œμ΄λ„ˆκ°€ μ’…λ£Œλ˜μ§€ μ•ŠμœΌλ©΄ SIGKILL μ‹ ν˜Έλ₯Ό λ³΄λƒ…λ‹ˆλ‹€.)

  • κΈ°μ‘΄ μ½”λ“œμ—μ„œλŠ” signal.Notify(channel, syscall.SIGINT)λ₯Ό μ‚¬μš©ν•˜κ³  μžˆμ–΄ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ μ’…λ£Œ μ‚¬μΈμœΌλ‘œ SIGINT μ‹ ν˜Έλ§Œμ„ μ²˜λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.
    즉 docker stop <container id> λͺ…λ Ή 사인을 μ²˜λ¦¬ν•˜μ§€ λͺ»ν•˜κΈ° λ•Œλ¬Έμ—, ν˜„μž¬ μ½”λ“œμ—μ„œλŠ” docker container κ°€ 내렀가도 goroutine 둜직이 μ •μƒμ μœΌλ‘œ μˆ˜ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

  • 도컀 μ»¨ν…Œμ΄λ„ˆκ°€ κ°€ stop 될 λ•Œλ„ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•΄μ„  signal.Notify() μ½”λ“œ 내뢀에 도컀 μ»¨ν…Œμ΄λ„ˆκ°€ stop 될 λ•Œμ˜ μ‹œκ·Έλ„μ„ 인자둜 μΆ”κ°€ν•˜λ©΄ λ©λ‹ˆλ‹€. κ°œμ„ λœ μ½”λ“œλŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

func (s *Server) StartServer() error {

    s.setServerInfo()
    channel := make(chan os.Signal, 1)
    signal.Notify(channel, syscall.SIGINT, syscall.SIGTERM을) // λ‹¨μˆœ μ’…λ£Œ μ‹œκ·Έλ„ + Docker container stop μ‹œκ·Έλ„

    go func() {
        <-channel
      // 둜직 μˆ˜ν–‰
        os.Exit(1)
    }()
    return s.engine.Listen(s.port)
}
  • 이제 λ¬Έμ œκ°€ λͺ¨λ‘ ν•΄κ²°λμ„κΉŒμš”? μ΄λ‘ μƒμœΌλ‘œλŠ” κ·Έλ ‡μŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ œκ°€ μ‹€ν–‰ν–ˆμ„ 땐 μ—¬μ „νžˆ λ¬Έμ œκ°€ 풀리지 μ•Šλ”λΌκ΅¬μš”. (μ§„λ¦¬μ˜ 제 컴에선 μ•ˆλΌμš” γ… )

  • GPT 의 도움을 λ°›μ•˜μŠ΅λ‹ˆλ‹€.

  • Docker PID κ°€ 1이 아닐 수 μžˆλ‚˜? λΌλŠ” 생각이 λ“€μ—ˆμ§€λ§Œ, ν™•μ‹€μΉ˜ μ•Šμ•˜κΈ°μ— μ‹€ν–‰ 쀑인 μ»¨ν…Œμ΄λ„ˆμ˜ PID λ₯Ό ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

  • μ•„λ‹ˆλ‚˜ λ‹€λ₯ΌκΉŒ PID λŠ” 1이 μ•„λ‹ˆμ—ˆμŠ΅λ‹ˆλ‹€. πŸ₯²πŸ₯²

$ docker top <container id> # container PID 확인

μ»¨ν…Œμ΄λ„ˆμ˜ PID 1이 μ€‘μš”ν•œ 이유:

  • PID 1 ν”„λ‘œμ„ΈμŠ€λŠ” λ¦¬λˆ…μŠ€μ—μ„œ μ€‘μš”ν•œ 역할을 ν•©λ‹ˆλ‹€. λ¦¬λˆ…μŠ€ μ‹œμŠ€ν…œμ—μ„œ PID 1은 μ΅œμƒμœ„ ν”„λ‘œμ„ΈμŠ€μ΄λ©° μ‹œμŠ€ν…œμ—μ„œ λ°œμƒν•˜λŠ” μ‹ ν˜Έλ₯Ό μ²˜λ¦¬ν•˜κ³  μžμ‹ ν”„λ‘œμ„ΈμŠ€κ°€ μ’…λ£Œλ  λ•Œ μ’€λΉ„ ν”„λ‘œμ„ΈμŠ€λ₯Ό μ²­μ†Œν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€.

  • μ»¨ν…Œμ΄λ„ˆ ν™˜κ²½μ—μ„œλ„ `PID 1 ν”„λ‘œμ„ΈμŠ€λŠ” 도컀가 μ „λ‹¬ν•˜λŠ” μ‹ ν˜Έ(SIGTERM, SIGINT λ“±)λ₯Ό μ²˜λ¦¬ν•˜κ³ , μ»¨ν…Œμ΄λ„ˆ μ’…λ£Œ μ‹œ μ œλŒ€λ‘œ 된 정리 μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.

  • λ”°λΌμ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ PID 1둜 μ‹€ν–‰λ˜μ§€ μ•ŠμœΌλ©΄ μ‹ ν˜Έλ₯Ό μ œλŒ€λ‘œ μˆ˜μ‹ ν•  수 μ—†κΈ° λ•Œλ¬Έμ— μ˜ˆμƒν•œλŒ€λ‘œ 둜직이 μ‹€ν–‰λ˜μ§€ μ•Šμ•˜λ˜ κ²ƒμ΄μ—ˆμŠ΅λ‹ˆλ‹€.

μ»¨ν…Œμ΄λ„ˆμ˜ PID κ°€ 1이 μ•„λ‹ˆμ—ˆλ˜ 이유

  • μ €λŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό 도컀 컴포즈둜 μ‹€ν–‰ν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

  • 컴포즈둜 μ‹€ν–‰ν•˜λŠ”κ±΄ λ¬Έμ œκ°€ μ•ˆλ˜μ§€λ§Œ μ»¨ν…Œμ΄λ„ˆμ˜ PID λ₯Ό 1둜 μ‹€ν–‰ν•˜κΈ° μœ„ν•΄μ„  컴포즈 파일 λ‚΄λΆ€μ˜ λͺ…λ Ήμ–΄ μˆ˜μ •μ΄ ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. κΈ°μ‘΄ 도컀 컴포즈 νŒŒμΌμ€ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

version: '3'

services:
  chat_backend_1:
    build:
      context: ./chat_backend
      dockerfile: Dockerfile
    ports:
      - "1011:1010"
    volumes:
      - ./chat_backend:/go/src/app
    working_dir: /go/src/app
    environment:
      - GO111MODULE=on
    command: sh -c "go build -o main . && ./main" # << 변경이 ν•„μš”ν•œ 지점

  # ... μ΄ν•˜ μƒλž΅
  • μ»¨ν…Œμ΄λ„ˆκ°€ μ‹€ν–‰λ˜λŠ” ν”„λ‘œμ„ΈμŠ€κ°€ PID 1이 λ˜λ„λ‘ ν•˜λ €λ©΄, command μ„Ήμ…˜μ—μ„œ μ‰˜ μŠ€ν¬λ¦½νŠΈλ‚˜ λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•  λ•Œ exec λͺ…령을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

  • execλŠ” ν˜„μž¬ μ‰˜ ν”„λ‘œμ„ΈμŠ€λ₯Ό λŒ€μ²΄ν•˜κΈ° λ•Œλ¬Έμ—, μ‹€ν–‰λ˜λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ PID λ₯Ό 1둜 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

version: '3'

services:
  chat_backend_1:
    build:
      context: ./chat_backend
      dockerfile: Dockerfile
    ports:
      - "1011:1010"
    volumes:
      - ./chat_backend:/go/src/app
    working_dir: /go/src/app
    environment:
      - GO111MODULE=on
    command: sh -c "go build -o main . && exec ./main" # << exec μΆ”κ°€

  # ... μ΄ν•˜ μƒλž΅
  • λ³€κ²½λœ 컴포즈 νŒŒμΌμ„ λΉŒλ“œν•˜μ—¬ 도컀 컴포즈λ₯Ό μ‹€ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. 결과적으둜 PID κ°€ 1둜 잘 좜λ ₯λ˜λŠ” κ±Έ 확인할 수 μžˆμ—ˆκ³ ,

    PID 1 둜 μ»¨ν…Œμ΄λ„ˆλ₯Ό μ‹€ν–‰ν•œ ν›„μ—λŠ” μœ„ goroutine μ½”λ“œκ°€ μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜λŠ”κ±Έ 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

κ²°λ‘ :

  • Go μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ 도컀 μ»¨ν…Œμ΄λ„ˆλ‘œ μš΄μ˜ν•  λ•Œ λ°œμƒν•  수 μžˆλŠ” μ˜ˆμƒμΉ˜ λͺ»ν•œ λ¬Έμ œμ™€ ν•΄κ²° 과정에 λŒ€ν•΄ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

    • μ‹ ν˜Έ 처리의 μ€‘μš”μ„±: UNIX 계열 μš΄μ˜μ²΄μ œμ—μ„œ μ‚¬μš©λ˜λŠ” μ‹ ν˜Έ SIGINT 와 SIGTERM 의 차이λ₯Ό μ΄ν•΄ν•˜κ³ , μ»¨ν…Œμ΄λ„ˆ ν™˜κ²½μ—μ„œ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ μ μ ˆν•œ λͺ…λ Ήμ–΄λ₯Ό μˆ™μ§€ν•΄μ•Ό ν•˜λŠ” 점을 λ°°μ› μŠ΅λ‹ˆλ‹€. 특히 도컀 μ»¨ν…Œμ΄λ„ˆμ—μ„œλŠ” SIGTERM μ‹ ν˜Έ μ²˜λ¦¬κ°€ ν•„μˆ˜!

    • PID 1의 μ—­ν• : μ»¨ν…Œμ΄λ„ˆ λ‚΄μ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ PID 1둜 μ‹€ν–‰λ˜μ–΄μ•Ό μ‹ ν˜Έλ₯Ό μ œλŒ€λ‘œ λ°›μ•„ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    • 도컀 컴포즈 μ„€μ •: exec λͺ…λ Ήμ–΄λ₯Ό μ‚¬μš©ν•˜μ—¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ PID 1둜 μ‹€ν–‰ν•΄μ•Ό ν•˜λŠ” 점을 λ°°μ› μŠ΅λ‹ˆλ‹€. μ»¨ν…Œμ΄λ„ˆ μš΄μ˜μ— μžˆμ–΄ μ€‘μš”ν•œ 팁이라 μƒκ°ν•©λ‹ˆλ‹€.