✔️ C
Language
- 컴파일 : C, C++, Golang, Rust
- Java : 컴파일을 하지만 바이트 코드를 만들어낸다. CPU가 바로 알아들을 수 없다. JVM에 의해 해석된다.
- .NET(C#) - .NET 프레임 워크
컴파일 과정을 통해 실행 파일을 만들어줘야 한다.
CPU가 바로 실행할 수 있는 실행 파일을 만들기때문에 속도가 매우 빠르다.
- 스크립트 : Shell, Perl, Python, Ruby, Javascript
- 인터프리터/런타임 이 필요하다.
소스코드 그대로를 실행하며 인터프리터가 실시간으로 해석하여 커널에게 넘기고 CPU에게 넘긴다.
#include <stdio.h>
int main()
print("Hello C World\n");
return 0;
}
소스 코드를 바로 실행할 수 없다. 컴파일해서 실행 파일로 만들어줘야 한다.
✔️ gcc 컴파일러
리눅스에서는 C 언어 컴파일에 gcc라는 컴파일러를 사용한다.
vagrant@docker ~/clang sudo apt install gcc
(venv) vagrant@docker ~/clang gcc hello.c -o hello
(venv) vagrant@docker ~/clang ls
hello hello.c
(venv) vagrant@docker ~/clang file hello
hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2116073347e3a13dedb56271596ee4e6fb4050b0, for GNU/Linux 3.2.0, not stripped
(venv) vagrant@docker ~/clang ./hello
Hello C World
이미지로 빌드해보자
vagrant@docker ~/clang ls
hello hello.c lib64
vagrant@docker ~/clang vi Dockerfile
vagrant@docker ~/clang cat Dockerfile
FROM ubuntu:focal
ADD hello /root
CMD ["/root/hello"]
vagrant@docker ~/clang docker build -t chello .
Sending build context to Docker daemon 20.99kB
Step 1/3 : FROM ubuntu:focal
---> 53df61775e88
Step 2/3 : ADD hello /root
---> ad4d42312613
Step 3/3 : CMD ["/root/hello"]
---> Running in 0a22f7b64ef1
Removing intermediate container 0a22f7b64ef1
---> d2d6cf2cb7cb
Successfully built d2d6cf2cb7cb
Successfully tagged chello:latest
vagrant@docker ~/clang docker run chello
Hello C World
vagrant@docker ~/clang docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
chello latest d2d6cf2cb7cb About a minute ago 72.8MB
hello world 하나 찍으려고 72.8MB 크기의 이미지를 사용한다. 이는 효율적이지 않다.
사실 이미지를 실행시키기 위해서는 hello라는 파일만 있으면 된다.
vagrant@docker ~/clang ls -lh hello
-rwxrwxr-x 1 vagrant vagrant 17K May 11 15:29 hello
용량이 17K 밖에 안된다. 이 파일만 실행시킬 수 있다면 효율성이 매우 높아질 것이다.
어떻게 용량을 줄일 수 있을까 ?
✔️ Scratch Image
Dockerfile을 이렇게 구성하면 되지 않을까 ?
ADD hello /
CMD ["/hello"]
하지만 이렇게 빌드하면 실패한다.
왜 빌드가 안될까? → FROM이 없으면 이미지 build가 안된다.
이 문제를 해결하기위해 만들어진 것이 scratch 이미지이다.
scratch 이미지는 아무 내용이 없는 이미지이다.
vagrant@docker ~/clang vi Dockerfile
vagrant@docker ~/clang cat Dockerfile
FROM scratch
ADD hello /
CMD ["/hello"]
vagrant@docker ~/clang docker run chello
standard_init_linux.go:228: exec user process caused: no such file or directory
하지만 FROM에 scratch 이미지를 지정하고 실행해도 오류가 난다.
여기서는 왜 또 안될까 ?
vagrant@docker ~/clang file hello
hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=97427f973fea912ba5a773fcd5e09360b644ef84, for GNU/Linux 3.2.0, not stripped
hello 파일의 형식을 보면 elf 파일(실행 파일)이며 dynamically linked
라고 되어있다.
hello 실행 파일을 실행하기 위해서는 몇몇 라이브러리가 필요하다는 메세지를 출력하고 있다.
그 라이브러리가 뒤에 나와있는 /lib64/ld-linux-x86-64.so.2 (인터프리터)이다.
해당 인터프리터를 살펴 보자
✔️ ldd 명령어
ldd hello
ldd
명령을 통해 hello가 사용하는 라이브러리의 리스트를 볼 수 있다.
그리고 확인한 라이브러리를 이미지 내에 포함시켜야 실행할 수 있다.
hello 라는 프로그램이 실행되기 위해서는 해당 라이브러리를 참조하는 것이다.
vagrant@docker ~/clang ldd hello
linux-vdso.so.1 (0x00007ffce5fa7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4601c0d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4601e0d000)
vagrant@docker ~/clang ls -l /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Feb 25 04:42 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.31.so
vagrant@docker ~/clang
vagrant@docker ~/clang ls -l /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Feb 25 04:42 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.31.so
vagrant@docker ~/clang ls -l /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 32 Feb 25 04:42 /lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.31.so
Dockerfile에서 hello를 실행하기 위해서는 ADD Instruction에 해당 파일들을 추가해줘야 한다.
vagrant@docker ~/clang mkdir -p lib/x86_64-linux-gnu/
vagrant@docker ~/clang mkdir -p lib64
vagrant@docker ~/clang ls
Dockerfile hello hello.c lib lib64
vagrant@docker ~/clang ldd hello
linux-vdso.so.1 (0x00007fff13375000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f400418f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f400438f000)
vagrant@docker ~/clang cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu
vagrant@docker ~/clang
vagrant@docker ~/clang ls lib/x86_64-linux-gnu
libc.so.6
vagrant@docker ~/clang cp /lib64/ld-linux-x86-64.so.2 lib64/
vagrant@docker ~/clang
vagrant@docker ~/clang ls lib64
ld-linux-x86-64.so.2
vagrant@docker ~/clang
빈 디렉토리를 만들어 라이브러리를 복사한뒤 컨테이너의 내부에 제공하는 방식을 사용할 것이다.
참고로 linux-vdso.so.1은 경로가 아닌 메모리의 주소가 적혀있다. 메모리 주소에 올라 실행되고 있다는 것이다.
vagrant@docker ~/clang cat Dockerfile
FROM scratch
ADD hello /
ADD lib/ lib
ADD lib64/ lib64
CMD ["/hello"]
vagrant@docker ~/clang docker build -t chello .
Sending build context to Docker daemon 2.244MB
Step 1/5 : FROM scratch
--->
Step 2/5 : ADD hello /
---> dec9249ee1a2
Step 3/5 : ADD lib/ lib
---> 41d34c343172
Step 4/5 : ADD lib64/ lib64
---> 02043862df67
Step 5/5 : CMD ["/hello"]
---> Running in 36a58ebf8fe8
Removing intermediate container 36a58ebf8fe8
---> c439fc8ece76
Successfully built c439fc8ece76
Successfully tagged chello:latest
vagrant@docker ~/clang docker run chello
Hello C World
hello 실행 파일은 참조하는 라이브러리가 2개가 있는데
하나는 /lib/x86_64-linux-gnu/libc.so.6 이며 하나는 /lib64/ld-linux-x86-64.so.2 이다.
이미지로 빌드할 때는 hello 실행 파일과 두 라이브러리 파일 즉 3개의 파일을 묶어서 만든다.
이처럼 어떠한 프로그램이 혼자서 작동하는 일은 드물다. 라이브러리 의존성을 가진다.
그렇다면 hello world 이미지는 어떻게 만들어질까?
프로그램이 조금만 복잡해져도 필요로하는 라이브러리들이 엄청나게 많을거고 ldd
로 일일히 찾아서 넣는 것은 힘들다.
오히려 알파인 리눅스에 hello 실행 파일을 추가해 이미지로 만드는 것이 빠를 것이다.
✔️ static linked vs dynamic linked
vagrant@docker ~/clang gcc hello.c -o hello
vagrant@docker ~/clang gcc hello.c -o hellos -static
vagrant@docker ~/clang file hello
hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=97427f973fea912ba5a773fcd5e09360b644ef84, for GNU/Linux 3.2.0, not stripped
vagrant@docker ~/clang file hellos
hellos: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=892d801da71316deb3bcef57c98a278cf1fefa28, for GNU/Linux 3.2.0, not stripped
vagrant@docker ~/clang ls -lh hello hellos
-rwxrwxr-x 1 vagrant vagrant 17K May 11 22:41 hello
-rwxrwxr-x 1 vagrant vagrant 852K May 11 22:41 hellos
hellos는 두개의 라이브러리를 파일 내에 포함하고 있는 것이다. 즉, 실행 파일에 라이브러리를 넣어버린 것이다.
이런 방식을 static linked 방식이라고 한다.
하지만 모든 프로그램을 이렇게 만들면 프로그램의 크기가 커진다.
라이브러리란 자주 사용하는 프로그램을 미리 별도의 파일로 만들고 그것을 참조해서 쓰는 것이다.
매번 라이브러리를 실행 파일에 꾸겨 넣으면 실행 파일의 크기가 너무 커질 것이다.
vagrant@docker ~/clang vi Dockerfile
vagrant@docker ~/clang cat Dockerfile
FROM scratch
ADD hellos /
#ADD lib/ lib
#ADD lib64/ lib64
CMD ["/hellos"]
vagrant@docker ~/clang docker build -t chellos .
Sending build context to Docker daemon 3.117MB
Step 1/3 : FROM scratch
--->
Step 2/3 : ADD hellos /
---> ca500e27c1c7
Step 3/3 : CMD ["/hellos"]
---> Running in a0a07343f6e4
Removing intermediate container a0a07343f6e4
---> c77547794b39
Successfully built c77547794b39
Successfully tagged chellos:latest
vagrant@docker ~/clang docker run chellos
Hello C World
vagrant@docker ~/clang ldd hellos
not a dynamic executable
✘ vagrant@docker ~/clang
not a dynamic executable
: 참조하는 라이브러리 없이 단독으로 작동한다.
참고로 파이썬이나 node.js 같은 스크립트 언어는 이런 작업을 할 수 없다.
실행에 인터프리터가 필요하며 수많은 운영체제의 바이너리/라이브러리가 필요하기 때문에 사이즈를 줄일 수 없다.
✔️ multistage-build
https://docs.docker.com/develop/develop-images/multistage-build/
개발자가 하루에 한번씩 코드를 넘겨주면 이미지를 빌드해야한다고 생각해보자
어플리케이션의 크기가 커지면 빌드에 시간이 오래걸린다.
FROM golang:1.16-alpine AS build # build라는 이름으로 취급하겠다
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project # 이 파일을 넘긴다.
FROM scratch # AS build와 --from=build가 이어진다.
COPY --from=build /bin/project /bin/project # 여기로 넘긴다.
앞선 build의 파일
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
하나의 도커파일에 2개의 FROM이 있다. 이런 형식을 멀티 스테이지 빌드라고 한다.
첫번째 FROM에서 마지막에 go build
를 통해 /bin/project 를 만든다. 실행파일을 만드는 것과 같다.
중요한 것은 이 실행파일을 다음 이미지에 넘겨줘야 한다.
scratch는 단독으로 작동할 수 있는 static linked 방식을 사용한다.
따라서 첫번째 FROM에서는 실행 파일을 빌드하고 파일만 넘긴다. 실제로 이미지를 빌드하는 것이 아니다.
- AS build : build 라는 이름으로 취급하겠다는 뜻이다.
- COPY --from=build : 첫번째 build의 것을 사용하겠다는 뜻이다.
COPY나 ADD에서는 src가 있어야 하는데 다른 이미지 빌드의 산출물을 가져와서 쓰려면 이 방식을 사용한다.
불리는 이름은 상관없다. 만약 AS build 자리에 xyz를 입력하면 --from=xyz로 가져와서 쓰면 된다.
앞의 /bin/project는 첫번째 이미지 빌드시 생성된 실행 파일이다. 그 실행 파일을 scratch 이미지로 가져오는 것이다.
이제 개념을 이해했으니 다시 빌드해보자
gcc라는 이미지를 사용해서 빌드한다.
vagrant@docker ~/clang vi Dockerfile
vagrant@docker ~/clang cat Dockerfile
FROM gcc AS cbuilder
WORKDIR /root # 작업 디렉토리를 루트로 세팅
ADD hello.c . # 현재 디렉토리의 hello.c 파일을 root 밑에 복사하고
RUN gcc hello.c -static -o hello 소스 파일을 컴파일해 실행 파일로 만든다.
FROM scratch # 스크래치 이미지에서
COPY --from=cbuilder /root/hello / # cbuilder에서 온 /root/hello를 /에 복사한다.
#ADD lib/ lib
#ADD lib64/ lib64
CMD ["/hello"] # 그리고 실행한다.
vagrant@docker ~/clang docker build -t chellos .
Sending build context to Docker daemon 3.117MB
Step 1/7 : FROM gcc AS cbuilder
latest: Pulling from library/gcc
6aefca2dc61d: Pull complete
967757d56527: Pull complete
c357e2c68cb3: Pull complete
c766e27afb21: Pull complete
32a180f5cf85: Pull complete
769273b4685a: Pull complete
1d2548492740: Pull complete
02d970bb18ce: Pull complete
200080470695: Pull complete
Digest: sha256:027a0d57f250c674dc2c713d8c5b2c7c5631a766f2309e29626684f69652d590
Status: Downloaded newer image for gcc:latest
---> 935af2cd477d
Step 2/7 : WORKDIR /root
---> Running in 0a3865b16ce5
Removing intermediate container 0a3865b16ce5
---> 92a0854505e7
Step 3/7 : ADD hello.c .
---> b65cecff5656
Step 4/7 : RUN gcc hello.c -static -o hello
---> Running in 0a4cf37d9555
Removing intermediate container 0a4cf37d9555
---> 7d47b4558967
Step 5/7 : FROM scratch
--->
Step 6/7 : COPY --from=cbuilder /root/hello /
---> 9850a0ec8b56
Step 7/7 : CMD ["/hello"]
---> Running in 02c9469be696
Removing intermediate container 02c9469be696
---> a82b7a0cd2ac
Successfully built a82b7a0cd2ac
Successfully tagged chellos:latest
vagrant@docker ~/clang docker images | grep gcc
gcc latest 935af2cd477d 42 hours ago 1.27GB
vagrant@docker ~/clang docker run chellos
Hello C World
vagrant@docker ~/clang docker save chellos -o hello.tar
vagrant@docker ~/clang ls
Dockerfile hello hello.c hello.tar hellos lib lib64
vagrant@docker ~/clang mkdir source
vagrant@docker ~/clang tar xf hello.tar -C source
vagrant@docker ~/clang cd source
vagrant@docker ~/clang/source ls
98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84 a82b7a0cd2ac1c82aef7e3da0a038a797b771d79459743e2e3702fa794b67fd0.json manifest.json repositories
vagrant@docker ~/clang/source cd 98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84
vagrant@docker ~/clang/source/98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84 tar xf layer.tar
vagrant@docker ~/clang/source/98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84 ls
VERSION hello json layer.tar
vagrant@docker ~/clang/source/98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84
vagrant@docker ~/clang/source/98b4b7a900a67d1d0bb29c92ff02b96854954fafbb15cfb570fc96cfb0538e84 ./hello
Hello C World
컴파일이 가능한 언어나 작업을 미리 진행해야하는 경우 멀티 스테이지 빌드로 한꺼번에 작업하면 편리하다.
'DevOps > Docker' 카테고리의 다른 글
[Docker] Django App을 Docker 이미지로 빌드하기 (0) | 2022.05.12 |
---|---|
[Docker] Flask App을 Docker 이미지로 빌드하기 (0) | 2022.05.12 |
[Docker] Alpine Linux (알파인 리눅스) (0) | 2022.05.11 |
[Docker] Dockerfile 이미지 빌드와 Cache (0) | 2022.05.11 |
[Docker] ubuntu 이미지로 httpd 서비스 이미지 만들기 ( + Timezone 세팅) (0) | 2022.05.11 |
영차영차 성장 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!