package main
import(
"fmt"
"time"
)
func main() {
for {
fmt.Println("Hello, world!")
time.Sleep(10 * time.Second)
}
}
10초에 한 번씩 Hello, world!를 출력하는 Go 프로그램이다.
이 코드를 Docker 이미지로 만들어서 github actioin을 통해 DockerHub에 푸쉬를 해볼 것이다.
보통은 이런 방법으로 Docker 이미지를 만든다.
FROM golang:1.11
WORKDIR /usr/src/app
COPY . .
CMD ["main"]
이렇게 이미지를 만들면 golang:1.11
베이스 이미지의 크기가 크기 때문에 실제 go의 바이너리 크기는 2MB이지만 전체 이미지의 크기는 778MB에 달한다.
따라서 바이너리만 독립적으로 실행 가능하게 만들어서 최적화해야한다.
Scratch는 텅 비어있는 이미지로 super minimal image를 만들기에 유용하다고 한다.
FROM scratch
WORKDIR /usr/src/app
COPY . .
CMD ["main"]
이 Dockerfile을 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s' -o main main.go
명령어를 통해 Docker 이미지를 만들어야하는데
이렇게 이미지를 만들면 바이너리 외에는 아무것도 들어있지 않기 때문에 1.36MB의 크기로 최적화된다.
하지만 매번 빌드할 때마다 옵션을 주어야하고 Go 컴파일러가 필요하기 때문에 CI/CD 환경에 설치해야하는 번거로움이 있다.
따라서 Dockerfile에서 바이너리 빌드와 실행 이미지 빌드를 한 번에 실행하는 방법을 사용해야한다.
과거에는 이런 방법을 사용하려면 바이너리를 빌드하기 위한 파일 하나, 빌드한 바이너리를 실행하기 위한 경량화된 이미지 하나 해서 총 두 개 이상의 Dockerfile이 필요했다.
여기서 사람들이 불편함을 느껴 여러 단계의 이미지 빌드 과정을 하나의 Dockerfile에서 관리하기 위해 Multi stage build가 개발되었다.
### Builder
FROM golang:1.13-alpine as builder
RUN apk update && apk add git
WORKDIR /usr/src/app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s' -o main .
### Make executable image
FROM scratch
COPY --from=builder /usr/src/app .
CMD [ "/main" ]
처음 Builder 부분은 builder라는 이름의 스테이지로 Go 바이너리를 빌드하는 단계이다.
RUN apk ... 부분은 추가적인 바이너리를 추가하는 부분이다. Go는 의존성 관리를 git으로 하는데 alpine 이미지에는 git이 없다. 따라서 git을 추가한다.(CA 인증서나 /etc/passwd 등 프로그램에 따라 추가해줘야한다.)
golang:1.13-alpine
이미지를 이용해 소스 코드를 바이너리 형태로 빌드하고 결과가 main 파일로 /usr/src/app 경로에 생성된다.
두 번째 FROM 스테이지는 COPY 명령어를 통해 DockerHost가 아닌 builder 스테이지로부터 실행한다는 뜻이다.
따라서 builder 스테이지의 /usr/src/app 폴더를 .로 복사한다.
builder 스테이지에서 바이너리 이름을 main으로 했기 때문에 main을 실행시킨다.
이미지는 1.67MB의 크기로 builder 스테이지의 파일들은 최종 이미지에 포함되어 있지 않는다.(마지막 스테이지가 최종 이미지에 들어간다.)
이렇게 만든 이미지를 가지고 Github actions과 Docker hub빌드 시스템을 편리하게 구성할 수 있다.
github 레포지토리의 actions에 들어가서 새로운 workflow를 생성하면 .github/workflows라는 폴더 안에 .yaml파일이 생성된다.
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Build
run: go build -v ./...
(원래는 테스트 코드도 작성해서 name: test run: go test ~ 처럼 해줘야한다.)
도커 이미지를 빌드하고, AWS 등에 push하면 AWS CodeDeploy등을 통해 배포까지 자동화가 가능하다.
여기서는 간단하게 Docker hub에 이미지를 push하는 것 까지만 할 것이다.(Docker hub에 push까지 자동이고 실행은 하지않음)
위에 만들어놓은 Dockerfile을 사용해서 Docker hub에 push해보자
먼저 Docker hub에 회원가입을하고 Github Actions 안에서 Docker hub에 로그인해야 push할 수 있으므로 yaml파일을 수정해준다.
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Build
run: go build -v ./...
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_TOKEN}}
${{secrets.~~}}과 같은 것은 Github 레포지토리에서 생성하는 secrets를 사용하는 것이다.(Github settings -> Secrets에서 Repository secrets에다 DOCKERHUB_USERNAME, DOCKERHUB_TOKEN을 만들어준다.)
Dockerhub username은 실제 Dockerhub 프로필 이름을 쓰면되고 Dockerhub Token 생성 방법은 여기를 참고해서 만들면된다.
다음으로 Docker hub에 actiontest라는 이름의 레포지토리를 하나 만들어준다.
Docker commands를 보면 알겠지만 Docker hub에 push하기 위해서는 docker push 계정명/레포지토리명:tagname
을 통해 push할 수 있다.(여기서는 tagname을 간단하게 latest로 사용한다.)
다시 yaml파일을 Docker 이미지를 빌드하고 Docker hub에 push하는 코드를 추가하자
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Build
run: go build -v ./...
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_TOKEN}}
- name: build and release to DockerHub
env:
NAME: soosungp33 # 계정 명
REPO: actiontest # 레포 이름
run: |
docker build -t $REPO .
docker tag $REPO:latest $NAME/$REPO:latest
docker push $NAME/$REPO:latest
docker build -t $REPO .
: Dockerfile을 이용해 레포지토리 이름을 딴 이미지를 만들어 준다.docker tag $REPO:latest $NAME/$REPO:latest
: 이미지를 그대로 push하면 경로가 맞지 않아서 push할 수 없으므로 tag 명령어를 통해 계정명/레포지토리명:tagname
으로 이미지 이름을 수정한다.docker push $NAME/$REPO:latest
: 이미지를 Docker hub의 레포지토리에 push한다.파일을 아무거나 수정하고 github에 push를 해보면 성공적으로 Docker hub에 이미지가 올라가게된다!!