[Golang] Github Actions를 통해 DockerHub에 올리기

김영한·2021년 3월 26일
1

Devops

목록 보기
3/4

참고, 참고


1. 간단한 go 프로그램 짜기

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에 푸쉬를 해볼 것이다.

2. Docker 이미지 만들기

기본적인 방법

  1. 해당 언어의 베이스 이미지 사용
  2. 라이브러리 설치
  3. 실행 파일을 복사
  4. 명령어 설정

보통은 이런 방법으로 Docker 이미지를 만든다.

FROM golang:1.11
WORKDIR /usr/src/app
COPY . .
CMD ["main"]

이렇게 이미지를 만들면 golang:1.11 베이스 이미지의 크기가 크기 때문에 실제 go의 바이너리 크기는 2MB이지만 전체 이미지의 크기는 778MB에 달한다.

따라서 바이너리만 독립적으로 실행 가능하게 만들어서 최적화해야한다.

Scratch를 이용한 방법

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 이미지를 만들어야하는데

  • CGO_ENABLED=0 : Scratch 이미지에는 C 바이너리가 없기 때문에 cgo를 비활성화 후 빌드해야한다.
  • GOOS=linux GOARCH=amd64 : OS와 아키텍쳐 설정이다.
  • -a : 모든 의존 패키지를 cgo를 사용하지 않도록 재빌드
  • -ldflags '-s' : 배포단계까지 가면 디버그 정보가 필요없기 때문에 디버깅 정보를 제거해서 바이너리를 조금 더 경량화할 수 있다.

이렇게 이미지를 만들면 바이너리 외에는 아무것도 들어있지 않기 때문에 1.36MB의 크기로 최적화된다.

하지만 매번 빌드할 때마다 옵션을 주어야하고 Go 컴파일러가 필요하기 때문에 CI/CD 환경에 설치해야하는 번거로움이 있다.
따라서 Dockerfile에서 바이너리 빌드와 실행 이미지 빌드를 한 번에 실행하는 방법을 사용해야한다.

Multi stage build

과거에는 이런 방법을 사용하려면 바이너리를 빌드하기 위한 파일 하나, 빌드한 바이너리를 실행하기 위한 경량화된 이미지 하나 해서 총 두 개 이상의 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빌드 시스템을 편리하게 구성할 수 있다.

3. Github Actions으로 DockerHub에 올리기

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 ~ 처럼 해줘야한다.)

  • on : workflow를 트리거하는 이벤트 이름이다.
    • 위의 코드는 master 브랜치에 push나 PR이 트리거되면 동작한다는 의미이다.
    • types라는 조건을 붙여서 조건에 속하는 경우에만 workflow가 동작하게 할 수 있다.
  • job : workflow 실행은 하나 이상의 job으로 구성되고 기본적으로 병렬로 실행된다.
    • build, deploy 등 커스텀도 가능하다.
  • Steps : 가장 중요한 부분으로 command 명령을 실행할 수 있고 다른사람들이 만들어놓은 오픈 소스를 가져와 사용할 수 있다.
    • name은 임의로 자기가 설정할 수 있다.
    • uses를 통해 다른 사람이 만들어 놓은 것을 가져와 사용한다.
    • action/checkout@v2 : 현재 상태의 소스코드를 가상의 컨테이너 안으로 checkout해주는 역할이다.
    • actions/setup-go@v2 : 가상의 컨테이너 안에 go가 돌아갈 수 있는 환경을 설치하는 것이다.

도커 이미지를 빌드하고, 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에 이미지가 올라가게된다!!

0개의 댓글