beadss / docker-study

0 stars 0 forks source link

week(1) 도커개념, Dockerfile, 도커이미지 #3

Open joont92 opened 5 years ago

joont92 commented 5 years ago

도커개념

기존의 가상화 기술

기존에 우리가 알고 있는 가상화는 하이퍼바이저(VMM)라는 기술을 이용하여 구현된 것이다
하이퍼바이저는 호스트 컴퓨터에서 다수의 운영체제를 동시에 실행하기 위한 논리적 플랫폼을 말한다

하이퍼바이저를 이용한 가상화 방식에는 크게 2가지가 있다

Virtual Machine Type1(native)

호스트 OS 없이 하이퍼바이저가 하드웨어 바로 위에서 직접 동작하는 방식이다
중간에 OS가 없으므로 하드웨어에서 가상화 기술을 지원해줘야 하는 단점이 있지만, 요즘 CPU들은 대부분 가상화를 기본으로 지원하기 떄문에 그다지 단점이 되진 않는다

type1에서도 구현방식이 또 2가지로 나뉜다

전가상화

하드웨어 전체를 가상화한 뒤 가상머신을 올리는 방법을 말한다
모든 가상머신의 요청은 항상 하이퍼바이저를 통해서 가게 된다

반가상화

하드웨어 전체를 다 가상화하진 않는 방식을 말한다
특정 요청은 하드웨어에 직접 요청가능하고, 특정 요청은 하이퍼바이저를 통해서만 가능하다(CPU, 메모리 할당 등)
이러한 특성 때문에 OS를 조금 수정해줘야하는 단점이 있지만, 속도면에서는 당연히 더 빠르다는 장점이 있다

Virtual Machine Type2(hosted)

호스트 운영체제 위에서 하이퍼바이저가 동작하는 방식이다
우리가 잘 아는 VMWare, VirtualBox 등이 여기 속한다
당연히 속도는 더 느리다

컨테이너 가상화 기술?

우리가 원하는건 특정 환경에 종속되지 않은 상태로 어플리케이션을 띄우는 것이다
하지만 말 그대로, 단순히 어플리케이션만을 띄우고 싶을 뿐인데 OS까지 띄우는것은 엄청난 낭비이다
하지만 이러한 요구를 만족시키기 위해선 어플리케이션 격리를 해결해야 한다

필요한 것 : 격리된 CPU, 메모리, 디스크, 네트워크를 가진 공간을 만들고 이 공간에서 프로세스를 실행해서 유저에게 서비스

구글은 이를 위해 2006년 부터 개발을 시작했고, process containers 라는 기술을 개발하게 된다
이는 이후 Cgroup 이라는 기능으로 이름이 변경되고, 리눅스 메인 커널에 하나의 기능으로 들어가게 된다
Croup는 간단히 말해 프로세스들의 자원 사용(CPU, 메모리, 디스크, 네트워크 등)을 제한하고 격리시키는 기술이다

Cgroup가 계속 발전되는 동안 리눅스 커널은 추가적으로 네임스페이스 격리라는 기능을 추가했다
이는 리소스들을 서로 다른 네임스페이스 그룹으로 나눠서, 다른 네임스페이스에서 볼 수 없도록 하는 기술이다

이 Cgroups, namespaces를 표준으로 정의해둔 것을 OCI 스펙이라고 하고, 도커에서 사용했던 LXC나 현재 사용하는 runC는 모두 이 스펙을 구현한 구현체이다

Cgroupnamespace를 사용하면 기존의 가상머신과 비슷한 수준의 가상환경을 만들 수 있게 된다

그리고 프로그램, 프로세스와의 관계와 비슷한 컨테이너 이미지, 컨테이너 기술을 구현하여 최종적으로 어플리케이션 격리를 해결하게 된다(?)

프로세스의 데이터가 변경되더라도 원본 프로그램 이미지를 변경할 수 없다
컨테이너의 데이터가 변경되더라도 컨테이너 이미지의 내용을 변경할 수 없다

참고로 컨테이너 가상화 기술로(도커로) 윈도우 어플리케이션 실행은 어렵다
언급했듯이 Cgroups, namespace 등은 리눅스 기술이고, 이는 리눅스로 어플리케이션을 실행한다는 것을 뜻하기 때문이다

OS의 자원을 공유한다
네트워크 설정이 어렵다(프로세스에 IP 붙이는건 좀 웃기지 않나?)


도커란?

컨테이너 기반의 오픈소스 가상화 플랫폼을 말한다
앞서 언급했던 컨테이너 가상화 기술을 사용하는 것은 물론이고, 그것을 관리하기 위한 명령어들, 이미지 버전관리, 도커 레지스트리 등등의 많은 기능을 제공한다

이곳에 잘 설명되어 있다 https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html

제공하는 기능 외에 도커의 주요 장점을 간단히 살펴보면 아래와 같다

코드로 관리하는 인프라와 불변 인프라

알다시피 애플리케이션 배포는 항상 인프라의 가변성을 포함하고 있어서 문제가 많다
(어플리케이션이 동일하더라도 배포하는 서버의 환경에 따라 여러가지 문제를 가져올 수 있다)
이러한 문제를 해결하는 가장 좋은 방법은 역시 애플리케이션이 의존하는 환경의 차이를 가능한 한 배제하는 것 이다

도커는 특정 시점의 서버 상태를 복제하는 기능(docker image)과 이를 코드로 관리할 수 있는 기능(Dockerfile)을 제공한다
그리고 애플리케이션과 환경을 같이 묶어서 빌드할 수 있게 함으로써 기존에 존재하던 환경의 차이를 최소한으로 줄여버렸다

이로인해 높은 이식성 또한 갖추게 되었다
빌드된 이미지는 도커가 설치된 머신이라면 어디서든 실행할 수 있기 때문이다

구성관리의 용이함

일정규모를 넘는 시스템은 보통 여러개의 어플리케이션과 미들웨어를 조합하는 형태로 구성된다
도커를 사용한다고해도 이 부분은 여전히 어려운 문제이다

하지만 도커는 이 문제를 해결했다
설정파일을 사용하여 컨테이너 간 의존 관계와 시작 순서롤 제어할 수 있는 docker compose를 개발했고,
대규모 트래픽에 맞춰 어플리케이션을 오케스트레이션 해주는 docker swarm 을 개발했다
그리고 최근에는 끝판왕으로 등장한 구글의 쿠버네티스가 있다

이렇듯 도커는, 도커를 편리하게 사용할 수 있게 해주는 주변 도구가 잘 갖춰져 있다는 장점도 가지고 있다

이러한 장점으로 도커는 굉장히 빠른 속도로 전파되어갔고, 사실상 표준(de fecto)가 되어 새로운 개발스타일을 많이 만들어내게 되었다

도커 설치 및 실행

앞서 언급한 Cgroups, namespaces는 리눅스 기술이기 때문에 mac이나 Windows에서 바로 사용할 수 없다
그러므로 LinuxKit 이라는 경량 시스템을 실행하고, 그 위에서 컨테이너를 실행하는 구조로 동작한다
물론 직접 설치할 필요는 없고 mac, windows 용 도커 설치하고 실행 시 자동으로 같이 실행된다

설치 및 실행법은 좋은 글이 있어서 아래의 글로 대체한다
https://subicura.com/2017/01/19/docker-guide-for-beginners-2.html


Dockerfile

Dockerfile이란?

도커 이미지를 만들 때 꼭 필요한 설정파일이다
도커 이미지 빌드시에 이 파일의 인스트럭션들을 참조하여 이미지를 만든다

기본으로 Dockerfile 이라는 이름을 사용하고, 이름을 변경하고 싶다면 이미지 빌드시에 추가 옵션을 줘야한다

인스트럭션

Dockerfile 내에 있는 명령어들을 말한다
아래는 각 인스트럭션에 대해 간단히 나열한 것이며, 자세한 내용이 궁금하면 https://docs.docker.com/engine/reference/builder/ 를 참조한다

자주 사용하는 인스트럭션

예시로 사용할 자바 어플리케이션과 Dockerfile 이다

Test.java

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Dockerfile

FROM openjdk:8-jdk

COPY Test.java .
RUN javac Test.java

CMD ["java", "Test"]

FROM

해당 도커 이미지의 바탕이 될 베이스 이미지를 지정하는 인스트럭션이다
현재는 java 파일을 실행해야 하므로 openjdk를 떙겨오고 있는 모습이다
(openjdk 는 이미지명이며, 8-jdk는 태그명이다)
레지스트리를 따로 지정하지 않았기 때문에 도커 허브에서 땡겨온다

COPY

호스트 머신의 파일이나 디렉터리를 도커 컨테이너 안으로 복사하는 인스트럭션이다
이전에 만든 컨테이너안의 현재 디렉토리로 호스트 머신의 Test.java 를 복사하는 과정이다

RUN

도커 이미지를 실행할 컨테이너 안에서 실행할 명령을 정의한다
복사된 Test.java를 javac로 컴파일하는 과정이다
호스트 머신에 Test.class가 없나 확인하지 말자

CMD

컨테이너 안에서 실행할 프로세스(명령)를 지정한다
컴파일된 Test.class 파일을 실행하는 과정이다

작성법이 조금 특이한데, 총 3가지 작성법을 제공한다

CMD는 Dockerfile 내에 하나만 작성할 수 있다

만약 CMD를 여러개 작성한다면 가장 앞부분껀 전부 무시되고 가장 마지막에 있는 명령만이 실행된다

CMD ["javac", "Test.java"]
CMD ["java", "Test"]

했다가 안되서 찾아봤음...

CMD 명령 오버라이드도 가능하다

위와 같이 선언된 상황에서

$ docker container run javatest:latest echo joont92
$ joont92 # 출력

CMD 명령이 무시됨을 볼 수 있다

그 외 인스트럭션

ENTRYPOINT

CMD와 마찬가지로 컨테이너 안에서 실행될 프로세스(명령)를 지정하는 인스트럭션이다
CMD와 다른점은 조금 기준점(?) 이 되는 프로세스를 지정하는 것이랄까..
ENTRYPOINT를 입력하면 CMD에 전달된 인자들은 전부 ENTRYPOINT의 인자로 전달된다

FROM openjdk:jdk-8

ENTRYPOINT ["java"]
CMD ["version"]

또한 아래와 같이 사용해서 컨테이너의 용도를 어느정도 제한 할수도 있다

FROM golang:1.10

ENTRYPOINT ["go"]
CMD [""]

자동으로 go 가 입력된 상태라고 보면 된다
go 이후의 명령어만 인자로 넘기면 된다

$ docker container run gotest:latest version
$ go version go1.10.3 linux/amd64 # 출력

LABEL

이미지를 만든 사람의 이름 등을 적을 수 있다

LABEL maintainer="joont92@github.com"

ENV

도커 안에서 사용할 환경 변수를 지정한다

ENV CLASSPATH=/workspace/javatest

CMD ["java", "Main"]

ARG

이미지 빌드할 떄 환경변수를 전달받게끔 하기 위해 사용한다
이미지 빌드시에만 사용할 수 있다

ARG classpath
ENV CLASSPATH=${classpath}

CMD ["java", "Main"]

--build-arg를 통해 인자를 전달한다

$ docker image build --build-arg classpath=/workapce/javatest -t javatest:latest .

Docker image

도커는 크게 도커 이미지와 도커 컨테이너로 나뉜다
그 중 도커 이미지는 컨테이너를 생성하는 템플릿 역할을 한다

아래는 도커 이미지를 관리하면서 자주 사용하는 명령어들과 그에 대한 간단한 설명이다
추가적인 명령어나 옵션이 궁금하다면 https://docs.docker.com/engine/reference/commandline/image/ 를 참조한다

docker image build

$ docker image build [options] Dockerfile_경로

Dockerfile에 기술된 내용을 따라 도커 이미지를 생성하는 명령이다

Dockerfile은 필수로 필요하다

-t 옵션

$ docker image build -t javatest:latest .

도커 이미지에 이름과 태그를 붙이는 옵션으로 실제 사용에선 거의 필수적으로 쓰인다
지정한 경로에서 Dockerfile 이라는 이름의 파일을 찾고 그 내용을 토대로 이미지를 만든다

태그명은 생략 가능한데, 생략하면 기본적으로 latest가 붙는다

-f 옵션

$ docker image build -f Dockerfile_test -t javatest:latest .

Dockerfile이 아닌 다른 이름을 쓰고 싶을 경우 사용한다

--pull 옵션

$ docker image build --pull=true -t javatest:latest .

기본적으로 도커 이미지 빌드 시 FROM 인스트럭션에 지정한 이미지를 레지스트리에서 받은 후, 이를 호스트 운영체제 저장해놓고 재사용한다
이를 매번 새로 받아오게 하고 싶을 경우 사용하면 좋다

이 옵션은 아래와 같은 상황을 대비하기 위해 사용하면 좋다

FROM somelibrary:latest

...
  1. 도커 이미지를 빌드한다
    • FROM 인스트럭션의 somelibrary:latest 이미지를 받고 로컬에 저장해둔다
  2. somelibrary 이미지에 버그가 있어서 수정하고, 다시 latest 태그로 레지스트리에 올렸다
  3. 위의 Dockerfile 로 이미지를 새로 빌드한다
  4. 레지스트리의 변경사항이 생성된 이미지에 반영되지 않는다
    • somelibrary:latest가 이미 로컬에 있기 때문에 새로 받아오지 않고 재사용한다

이러한 이유로 실무에서는 latest 대신 보통 태그명을 직접 입력한다

docker search

$ docker search [options] 검색_키워드

도커 레지스트리에서 이미지를 검색할 때 사용한다
레지스트리를 따로 설정하지 않았다면 기본적으로 도커 허브가 사용된다

도커 허브(Docker Hub)?
도커 사 자체에서 관리하는 도커 이미지 레지스트리로, 깃허브처럼 자신의 계정이나 조직 이름으로 리포티저티를 만들고 이미지를 올릴 수 있다
도커 허브에는 이미 매우 많은 리포지터리가 등록되어 있어서, 직접 도커 이미지를 만들 필요없이 만들어놓은 이미지를 간단하게 사용할 수 있다

참고로 여기서 몇가지 용어의 혼재가 있을 수 있다

$ docker search mysql
$ docker search --limit 5 mysql # 5건만 검색

검색결과는 STARS(깃헙과 동일) 순으로 출력되고, 태그명까지는 검색할 수 없다

mysql 처럼 namespace가 없는 애들도 있는데 이는 mysql의 공식 리포지터리라서 그렇다

docker image pull

$ docker image pull [options] 리포지터리명[:태그명]

도커 레지스트리에서 도커 이미지를 내려받을 때 사용한다

$ docker image pull jenkins

태그명을 생략하면 기본으로 지정된 태그가 사용되는데 대부분 latest 이다

docker image ls

$ docker image ls [options] [이미지명[:태그명]]

현재 호스트 운영체제에 저장된 도커 이미지의 목록을 보여준다

참고로 여기서 조회되는 IMAGE ID는 CONTAINER ID와는 별개의 것이므로 주의해야 한다

docker image tag

도커는 이미지의 버전을 이미지 ID로 관리한다

  1. docker 파일을 빌드하여 이미지를 만든다
    $ docker image build -t javatest:latest .
  2. docker image ls로 빌드된 이미지가 잘 저장되었는지 확인한다

    $ docker image ls -a
    
    REPOSITORY     TAG        IMAGE ID      CREATED      SIZE  
    javatest       latest     57ca5e531d73  1 hour ago   4.41MB 
  3. Dockerfile을 조금 수정하고 다시 빌드하고, docker image ls 로 확인해본다

    $ docker image build -t javatest:latest .
    $ docker image ls -a  
    
    REPOSITORY     TAG        IMAGE ID      CREATED      SIZE  
    javatest       latest     57ca5e531d73  1 hour ago   4.40MB 
    <none>         <none>     2327e281c9b2  2 hour ago   4.41MB

    IMAGE ID가 다른 이미지가 새롭게 빌드되었음을 볼 수 있다
    이처럼 Dockerfile을 수정할 때 뿐만 아니라 COPY 되는 대상이 바뀌거나 하여도 새로운 이미지로 빌드된다
    즉 도커 이미지의 버전은 도커 이미지 ID 가 된다

보다시피 태그는 이미지 ID를 식별하기 위해 붙이는 일종의 별칭밖에 되지 않는다
도커 이미지를 새로 빌드하면서 기존에 사용하던 태그를 부여했으므로 기존에 해당 태그를 가지고 있던 이미지의 이미지명과 태그명이 <none>으로 변경된 것이다

$ docker image tag 기반이미지명[:태그] 새이미지명[:태그]

보통은 latest의 특정 시점에 버전 넘버를 태그로 붙이기위해 사용한다

$ docker image joont92/javatest:latest joont92/javatest:1.0.0

docker image push

$ docker image push [options] 리포지터리명[:태그]

레지스트리로 도커 허브를 사용할 경우 리포지터리명 앞에 자신의 도커 ID로 네임스페이스를 붙여줘야한다

# 네임스페이스를 추가해주고
$ docker image tag javatest:latest joont92/javatest:latest
# push
$ docker image push joont92/javatest:latest

도커허브-push 리포지터리에 올라갔음을 볼 수 있다