컨테이너에 ssh 하는건 나쁜가?

컨테이너에 ssh 하는건 나쁜가?

나는 최근에 도커, ECS 파게이트를 사용해서 이미지를 빌드하고 컨테이너를 띄우는 작업을 했다. 그 당시에는 알 수 없는 이유로 컨테이너가 계속 뜨지 않았고, cloud watch 로그에는 에러 로그가 찍히지 않았다. 그래서 EC2 instance를 사용할 때 처럼 ssh 접속을 하고 싶었고, 접속을 하려고 stack overflow 와 AWS 공식문서를 읽다가 상당히 많은 개발자들이 컨테이너에 ssh 접속을 한다는 건 정말 이상한 일, production 서버에 ssh 하고싶은 상황이 발생하다는 것 자체가 문제라고 한 코멘트를 읽었다. 컨테이너에 ssh 접속을 하는게 왜 나쁜지에 대해 설명한 글을 아래 부분 번역했다.

도커 컨테이너에서 SSH 서버를 돌린다면, 잘못하고 있는것이다.

사람들이 도커를 처음 사용할때 자주 하는 질문이 있다. ‘컨테이너 안에 어떻게 들어가나요?’ 그리고 다른 사람들의 대답은 이렇다. ‘컨테이너에서 ssh 돌려요.’ 이건 나쁜 방법이다. 이제 이 글에서는 컨테이너에서 ssh 돌리기가 왜 잘못됐는지 이유를 알아보고, ssh 대신 뭘 해야하는지에 대해 알아본다.

ssh 서버를 돌리는건 너무 좋아보인다. 컨테이너 안에 쉽게 들어갈 수 있게 해주기 때문이다. 대부분의 개발자들이 ssh를 매일 사용하고, 퍼블릭과 프라이빗 키, 패스워드리스 로그인, 키 에이전트, 포트 포워딩등에 친숙하다.

레디스 서버나 자바 웹서비스용 도커 이미지를 만드는 중이라고 가정하자. 다음은 몇가지 생각해볼만한 질문들이다.

  • SSH를 무엇 때문에 사용하려 하는가?

    보통 ssh 를 하려 하는 이유는, 백업, 로그 확인, 프로세스 재시작, 환경설정 바꾸기, gdb 또는 strace 와 같은 툴로 서버 디버깅 등이다. ssh 없이도 이런 활동을 할 수 있다.

  • 키와 패스워드 관리는 어떻게 할 것인가?

    대부분의 개발자들이 키와 패스워드를 이미지에 추가해서 굽거나, 볼륨에 넣는다. 키나 패스워드를 업데이트 해야 하는 상황에서는 어떻게 할건가? 이미지 안에 이들을 같이 넣고 구워버리면, 업데이트가 필요한 상황에서는 이미지를 재빌드하고, 재배포하고, 컨테이너를 재시작하는 일련의 과정을 거쳐야 한다.

    더 나은 방법은 키, 패스워드를 볼륨에 넣고 볼륨을 관리하는 것이다. 꽤 괜찮은 방법이지만 현격한 단점 몇가지가 있다. 해당 방법을 사용할 경우 컨테이너가 절대 볼륨에 write를 하지 않도록 해야 한다. 관리를 잘 못하면, 키와 패스워드가 오염되서 사용불가 상태가 될 수 있다. 만약 여러대의 컨테이너가 해당 키, 패스워드를 동시에 공유하는 상태라면 사태는 더 심각해진다.

  • 보안 업그레이드 관리는 어떻게 할 것인가?

    ssh 서버는 꽤 안전하다. 그러나, 여전히 몇가지 보안 이슈가 있다. 그래서 ssh 를 사용해서 컨테이너를 업그레이드 해야 할 필요성을 느끼게 될 것이다. 업그레이드를 하려면 이미지를 다시 재빌드 하고, 모든 컨테이너를 재시작 해야한다.

  • 그냥 ssh 서버를 붙여야 한다면?

    안됀다. ssh 서버만 붙이는게 아니라, Monit 이나 Supervisor같은 프로세스 매니저도 추가해야 한다. 도커는 1개의 프로세스만 관리하기 때문이다. 프로세스가 여러개 필요하다면, 더 상위레벨에서 프로세스들을 관리하는 무언가를 추가해야 하고, 그말은 즉슨 아주 가볍고 간단한 컨테이너를 그냥 복잡한 무언가로 바꾸는 것과 같다. 어플리케이션이 중지한다면, 도커에서 정보를 얻는게 아니고 프로세스 매니저에서 정보를 찾아야 하게 된다.

  • 앱을 컨테이너 안에 넣는 일을 하면서 동시에 정책과 보안 담당이라면?

    작은 기업에서는 이게 큰 문제가 되지 않는다. 하지만 큰 기업에서, 앱을 컨테이너에 넣는 역할을 담당하고 있다면 아마 원격 접근 정책을 관리는 다른 사람이 하고 있을 것이다. 큰 기업은 누가 접근을 할 수 있고, 어떤 종류의 관리 감독이 필요한지에 대한 빡빡한 정책을 갖고 있을 가능성이 높다. 이런 경우라면, 컨테이너에 ssh 하고 싶은 생각이 없을 것이다.

그런데 ssh 를 안하고 어떻게 백업, 로그 확인, 서비스 재시작 등등을 할 수 있을까?

  • 백업

    데이터는 볼륨에 있어야 한다. 다른 컨테이너를 실행할때, -- volumes-from 옵션을 주면 첫번째 컨테이너와 볼륨을 공유한다. 새 컨테이너는 백업을 수행하고, 필요한 데이터에 대한 접근도 할 수 있다.

  • 로그 확인

    볼륨을 사용하자. 만약 특정 디렉토리 안에 로그를 남기길 원한다면, 그리고 디렉토리가 볼륨이라면 로그 검사용 컨테이너를 돌릴 수 있다. (--volumes-from)그리고 로그를 확인할 수 있다.

  • 서비스 재시작

    모든 서비스는 시그널로 재시작하는게 좋다. 만약 /etc/ini.d/foo restart 또는 service foo restart 를 한다면, 프로세스에 특정 시그널을 보내게 된다. docker kill -s <signal> 시그널을 보낼 수 있다.

    어떤 서비스들은 시그널을 받지 않지만, 소켓으로 커맨드를 받는다. 만약 TCP 소켓이라면, 네트워크에 연결하면 된다. 만약 UNIX소켓이라면, 볼륨을 사용하면 된다. 컨테이너와 서비스를 세팅하고 특정 디렉토리안에 있는 소켓을 컨트롤하면, 그 디렉토리가 볼륨이다. 그러면 볼륨, 소켓을 사용할 수 있는 새 컨테이너를 실행할 수 있다.

    이 과정은 복잡하지 않다. 서비스가 foo 라는 소켓을 /var/run/foo.sock 이라는 소켓을 생성했고, 서비스를 깨끗하게 재시작하기 위해서 fooctl restart 라는 명령어를 실행해야 한다고 하자. 재시작을 하고 싶을 때는, 정확히 똑같은 이미지를 실행하지만, --volumes-from 옵션을 함께 사용해서 명령어를 오버라이드 한다. 예를 들면 다음과 같다.

    # Starting the service
    CID=$(docker run -d -v /var/run fooservice)
    # Restarting the service with a sidekick container
    docker run --volumes-from $CID fooservice fooctl restart
    
  • 환경설정 수정

    환경설정을 바꿔야 한다면 이미지 안에서 해야한다. 왜냐면 새 컨테이널르 시작하면 옛날 설정들이 안에 또 있을 거고, 변경사항을 모두 잃어버리게 되니까 말이다.

    만약 새 버추얼 호스트를 추가하는 등의 변경을 해야 한다면, 볼륨을 사용하면 된다. 이런 환경설정들은 볼륨 안에 있어야 하고, 볼륨은 특수 목적인 ‘설정 변경’ 용 컨테이너와 공유되어야 한다. 이 컨테이너 안에서는 뭐든지 사용할 수 있다. ssh 와 제일 좋아하는 에디터, 또는 API 콜을 받는 웹 서비스, 또는 외부 소스로 부터 정보를 갖고오는 크론탭 등등.

    걱정하는 사항은 서비스를 돌리는 컨테이너, 환경설정 업데이트용 컨테이너 두개로 분리해야 한다.

  • 서비스 디버깅

    이런 상황이 정말로 컨테이너에 접속하고 싶은 상황이다. 왜냐면 gdb, strace, 등등을 실행해야 하니까 말이다. 이럴때는, nsenter를 사용하면 된다.

결론

컨테이너에 ssh 접속하는건 그렇게 까지 나쁘지 않다. 도커 호스트에 접속하지 않아도 되어서 편하다.

하지만 ssh를 안할 이유도 많은 걸 알았고, 그리고 ssh를 사용하지 않고도 더 깨끗한 구조에서 원하는 모든 기능을 사용할 수 있다.

Reference
  • https://jpetazzo.github.io/2014/06/23/docker-ssh-considered-evil/

  • https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance-connect.html