2020. 8. 13. 00:21 ㆍ개발 이야기/오픈소스
안녕하세요. 해커의 개발일기입니다.
오늘은 별도의 빌드환경에서만 빌드가 되는 경우 Docker를 사용해서 임시로 build 환경을 구성하고
빌드된 바이너리만 가지고 새로운 Docker 이미지에 넣는 것을 해보려고 합니다.
이런게 왜 필요할까요?
오픈소스의 경우 대부분의 개발자들이 리눅스 환경에서 개발을 하기 때문에 윈도우에서 환경에서 개발하는 우리는 빌드할 때 에러가 자꾸 발생합니다.. 저를 예를 들면 저는 프로젝트에서 시계열 디비(tsdb)인 Prometheus(프로메테우스)를 커스텀해 솔루션의 모듈로 사용하고 있습니다. 그런데 빌드할 때 react 소스코드도 들어가고 이 소스코드를 다시 golang으로 감싸서 하나의 바이너리로 만드는 과정이 있는데요 .. 이 과정에서 윈도우 환경인 경우 정말 끊임 없는 에러를 발생시키는 것을 볼 수가 있습니다. 애초에 윈도우 환경을 염두하고 만들어진 프로젝트가 아니기 때문이죠. 처음에 개발은 했는데 배포가 안되서 아주 고통스러운 시간을 보냈답니다(회사는 알아주지 않죠ㅋㅋ) .. 그래서 저는 가상 머신을 만들어서 빌드하고 바이너리를 배포했었는데요 .. 정말 바보같은 짓이었다는것을 깨달았습니다.. Docker를 이용했으면 좋았을 것을!
예시로 prometheus의 Makefile을 보겠습니다.
# Needs to be defined before including Makefile.common to auto-generate targets
DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le s390x
REACT_APP_PATH = web/ui/react-app
REACT_APP_SOURCE_FILES = $(wildcard $(REACT_APP_PATH)/public/* $(REACT_APP_PATH)/src/* $(REACT_APP_PATH)/tsconfig.json)
REACT_APP_OUTPUT_DIR = web/ui/static/react
REACT_APP_NODE_MODULES_PATH = $(REACT_APP_PATH)/node_modules
REACT_APP_NPM_LICENSES_TARBALL = "npm_licenses.tar.bz2"
PROMTOOL = ./promtool
TSDB_BENCHMARK_NUM_METRICS ?= 1000
TSDB_BENCHMARK_DATASET ?= ./tsdb/testdata/20kseries.json
TSDB_BENCHMARK_OUTPUT_DIR ?= ./benchout
include Makefile.common
DOCKER_IMAGE_NAME ?= prometheus
$(REACT_APP_NODE_MODULES_PATH): $(REACT_APP_PATH)/package.json $(REACT_APP_PATH)/yarn.lock
cd $(REACT_APP_PATH) && yarn --frozen-lockfile
$(REACT_APP_OUTPUT_DIR): $(REACT_APP_NODE_MODULES_PATH) $(REACT_APP_SOURCE_FILES)
@echo ">> building React app"
@./scripts/build_react_app.sh
.PHONY: assets
assets: $(REACT_APP_OUTPUT_DIR)
@echo ">> writing assets"
# Un-setting GOOS and GOARCH here because the generated Go code is always the same,
# but the cached object code is incompatible between architectures and OSes (which
# breaks cross-building for different combinations on CI in the same container).
cd web/ui && GO111MODULE=$(GO111MODULE) GOOS= GOARCH= $(GO) generate -x -v $(GOOPTS)
@$(GOFMT) -w ./web/ui
.PHONY: react-app-lint
react-app-lint:
@echo ">> running React app linting"
cd $(REACT_APP_PATH) && yarn lint:ci
.PHONY: react-app-lint-fix
react-app-lint-fix:
@echo ">> running React app linting and fixing errors where possible"
cd $(REACT_APP_PATH) && yarn lint
.PHONY: react-app-test
react-app-test: | $(REACT_APP_NODE_MODULES_PATH) react-app-lint
@echo ">> running React app tests"
cd $(REACT_APP_PATH) && yarn test --no-watch --coverage
.PHONY: test
test: common-test react-app-test
.PHONY: npm_licenses
npm_licenses: $(REACT_APP_NODE_MODULES_PATH)
@echo ">> bundling npm licenses"
rm -f $(REACT_APP_NPM_LICENSES_TARBALL)
find $(REACT_APP_NODE_MODULES_PATH) -iname "license*" | tar cfj $(REACT_APP_NPM_LICENSES_TARBALL) --transform 's/^/npm_licenses\//' --files-from=-
.PHONY: tarball
tarball: npm_licenses common-tarball
.PHONY: docker
docker: npm_licenses common-docker
.PHONY: build
build: assets common-build
.PHONY: bench_tsdb
bench_tsdb: $(PROMU)
@echo ">> building promtool"
@GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) promtool
@echo ">> running benchmark, writing result to $(TSDB_BENCHMARK_OUTPUT_DIR)"
@$(PROMTOOL) tsdb bench write --metrics=$(TSDB_BENCHMARK_NUM_METRICS) --out=$(TSDB_BENCHMARK_OUTPUT_DIR) $(TSDB_BENCHMARK_DATASET)
@$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/cpu.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/cpuprof.svg
@$(GO) tool pprof --inuse_space -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mem.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/memprof.inuse.svg
@$(GO) tool pprof --alloc_space -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mem.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/memprof.alloc.svg
@$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/block.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/blockprof.svg
@$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mutex.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/mutexprof.svg
© 2020 GitHub, Inc.
이 도커 파일을 make build 명령어를 이용해서 돌려보시면 아주 엄청난 에러들을 확인할 수 가 있는데요. 반면에 리눅스 환경에서는 손쉽게 빌드가 되는것을 볼 수 가 있습니다. 다음은 원본 Docker file을 보겠습니다.
ARG ARCH="amd64"
ARG OS="linux"
FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
ARG ARCH="amd64"
ARG OS="linux"
COPY .build/${OS}-${ARCH}/prometheus /bin/prometheus
COPY .build/${OS}-${ARCH}/promtool /bin/promtool
COPY documentation/examples/prometheus.yml /etc/prometheus/prometheus.yml
COPY console_libraries/ /usr/share/prometheus/console_libraries/
COPY consoles/ /usr/share/prometheus/consoles/
COPY LICENSE /LICENSE
COPY NOTICE /NOTICE
COPY npm_licenses.tar.bz2 /npm_licenses.tar.bz2
RUN ln -s /usr/share/prometheus/console_libraries /usr/share/prometheus/consoles/ /etc/prometheus/
RUN mkdir -p /prometheus && \
chown -R nobody:nogroup etc/prometheus /prometheus
USER nobody
EXPOSE 9090
VOLUME [ "/prometheus" ]
WORKDIR /prometheus
ENTRYPOINT [ "/bin/prometheus" ]
CMD [ "--config.file=/etc/prometheus/prometheus.yml", \
"--storage.tsdb.path=/prometheus", \
"--web.console.libraries=/usr/share/prometheus/console_libraries", \
"--web.console.templates=/usr/share/prometheus/consoles" ]
간략하게 보면 이미 빌드된 prometheus, promtool 바이너리를 COPY해서 도커 이미지를 만드는 것을 볼 수 있습니다.
하지만 이 바이너리를 얻기위해서는 가상머신의 리눅스 환경이 필요했는데요. 이부분을 Dockerfile 하나로 만들어 보겠습니다.
설명을 하자면 golang:1.13.8 버전의 build-env라는 별칭을 가진 환경을 구성하고
여기에 소스코드를 모두 넣고 make build를 진행합니다.
# created by mskim 2020-08-06
# Compile stage
FROM golang:1.13.8 AS build-env
ADD . /prometheus
WORKDIR /prometheus
RUN apt-get update && apt-get install -y --no-install-recommends nodejs
RUN apt-get install -y --no-install-recommends npm \
bzip2
RUN chmod 744 ./scripts/build_react_app.sh
RUN apt-get install npm
RUN npm install -g yarn
RUN make npm_licenses
RUN make build
# make docker image stage
#ARG ARCH="amd64"
#ARG OS="linux"
FROM quay.io/prometheus/busybox-linux-amd64:latest
LABEL maintainer="mskim@ex-em.com"
COPY --from=build-env /prometheus/prometheus /bin/prometheus
COPY --from=build-env /prometheus/promtool /bin/promtool
COPY --from=build-env /prometheus/tsdb/tsdb /bin/tsdb
COPY --from=build-env /prometheus/documentation/examples/prometheus.yml /etc/prometheus/prometheus.yml
COPY --from=build-env /prometheus/console_libraries/ /usr/share/prometheus/console_libraries/
COPY --from=build-env /prometheus/consoles/ /usr/share/prometheus/consoles/
COPY --from=build-env /prometheus/npm_licenses.tar.bz2 /npm_licenses.tar.bz2
RUN ln -s /usr/share/prometheus/console_libraries /usr/share/prometheus/consoles/ /etc/prometheus/
RUN mkdir -p /prometheus && \
chown -R nobody:nogroup etc/prometheus /prometheus
USER nobody
EXPOSE 9090
VOLUME [ "/prometheus" ]
WORKDIR /prometheus
ENTRYPOINT [ "/bin/prometheus" ]
CMD [ "--config.file=/etc/prometheus/prometheus.yml", \
"--storage.tsdb.path=/prometheus", \
"--web.console.libraries=/usr/share/prometheus/console_libraries", \
빌드가 완료되면 COPY --from=build-env 를 통해서 바이너리만 새로운 busybox-linux 기반의 환경에 복사를 해오는데요. 이렇게 하면 별도의 가상머신이 없이도 간편하게 리눅스 환경을 임시로 구성해 빌드 환경을 구축할 수가 있습니다.
아마도 저와 같이 임시적인 리눅스 환경의 빌드머신이 필요한 경우가 있을 거라고 생각하는데요 .. 꼭 이렇게 사용해보시면 얼마나 편리한지 아실 수 있습니다. 이런 환경이라면 디스크 소모도 줄이고, 가상머신을 켜고 로그인하는 시간까지 줄여주니 아주 쾌적한 개발 환경을 구성할 수 있습니다.
저는 이 방법을 이용해서 linux환경의 빌드가 필요한 어플리케이션들의 Dockerfile을 모조리 변경했는데요. 또 다른 예시를 보겠습니다. 심지어 의존성 문제 때문에 아주 오래된 가상머신에서만 돌아가는 app이었는데요 .. 원본의 Dockerfile을 보겠습니다.
FROM python:3.6-slim-stretch
RUN apt-get update && apt-get install -y --no-install-recommends \
iptables \
libltdl7 \
procps \
net-tools \
util-linux
WORKDIR /
COPY agent /
COPY SparseLogDetect.py /
COPY Logrotate /
COPY Logremove /
RUN mkdir /sparselog/
CMD ["/agent"]
이미 빌드가 되어있다면 아주 간편하게 Docker image로 만들 수 있는데요. 빌드환경이 아주 그지같았고 의존성 문제 때문에 개발하기도 아주 힘들었던 환경이었습니다.
임시 빌드 환경을 구성한 Dockerfile을 보겠습니다.
# Compile stage
FROM golang:1.13.8 AS build-env
# Build Delve
RUN go get github.com/go-delve/delve/cmd/dlv
ADD . /test
WORKDIR /test/cmd/agent
RUN go build -o /agent
FROM python:3.6-slim-stretch
RUN apt-get update && apt-get install -y --no-install-recommends \
iptables \
libltdl7 \
procps \
net-tools \
util-linux
WORKDIR /
COPY --from=build-env /agent /
CMD ["/agent"]
간략히 설명을 하면 build-env 환경에 소스코드를 모조리 옮겨서 빌드 후 역시나 COPY --from 옵션을 통해 빌드된 바이너리만 새로운 환경으로 이식하는 Dockerfile입니다. 이렇게 구성한다면 아주 간단히 빌드 머신을 구성할 수가 있습니다.
golang 뿐만이 아니라 java python C 모든 언어의 불필요한 빌드 환경을 쾌적하게 만들어줄 수 있다고 생각합니다. 꼭 한번 구성해보셔서 쾌적한 개발환경을 구성하시길 추천드립니다!
'개발 이야기 > 오픈소스' 카테고리의 다른 글
# IaaS 오픈소스 OpenStack 개요, OpenStack SDK 사용법(Golang) (4) | 2020.11.16 |
---|---|
# Docker를 이용해 DB 셋팅하기 - postgresql (2) | 2020.08.13 |
# Git 사용하기, conflict 처리하기, branch 만들기 (0) | 2020.05.05 |
# 오픈소스 OpenTracing - Jaeger (0) | 2020.03.25 |
# 오픈소스 분산형 NoSQL DBMS Cassandra(카산드라) (0) | 2020.03.17 |