# go 기반 컨테이너환경 구성

안규원·2024년 9월 5일
0

Infra

목록 보기
21/23

Go download


공식홈페이지 접속: https://go.dev/
클라우드 서버에 다운로드하므로 tar 다운로드 링크 복사

$ wget https://go.dev/dl/go1.23.0.linux-amd64.tar.gz

# 이후 Docs 참조해 진행. 관리자 권한으로 실행
$ sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.0.linux-amd64.tar.gz

$ vim $HOME/.profile

# 아래의 내용 추가
export PATH=$PATH:/usr/local/go/bin

$ source ~/.profile
$ go version
go version go1.23.0 linux/amd64

이후 VS code extension에서 go plugin 설치(클라우드에)


Go 기본문법

# hello world 위한 디렉토리 생성
$ mkdir -p ~/projects/box

# go 모듈 생성
$ go mod init example/box
go: creating new go.mod: module example/box

$ ls -l
total 4
-rw-rw-r-- 1 ubuntu ubuntu 30 Sep  5 04:46 go.mod

$ vi main.go

main.go

package main

import "fmt"

func main() {
	fmt.Println("Hello Go")
}

main.go 실행

$ go run main.go
Hello Go

외부패키지 import

import (
	"fmt"
	"rsc.io/quote"
)

패키지 의존성 관리

$ go mod tidy
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

main.go 실행

$ go run main.go
Ahoy, world!

스레드 간 통신하는 채널

package main

import (
	"fmt"
	"time"
)

func readword(ch chan string) {
	fmt.Println("Type a word, then hit Enter.")
	var word string
	fmt.Scanf("%s", &word)
	ch <- word
}

func printchar() {
	// while문과 유사
	for {
		fmt.Printf(".")
		time.Sleep(2 * time.Second)
	}
}

func main() {
	// 지연처리. 앱 종료 후 처리됨
	defer fmt.Println("===== BYE..")
    // 새로운 스레드 생성
	go printchar()

	// 채널 생성
	ch := make(chan string)
	go readword(ch)

	// Switch와 유사
	select {
	case word := <-ch:
		fmt.Println("\nReceived: ", word)
	}
}

주요 go 커맨드

# go 바이너리 빌드
$ go build

# go 빌드/실행
$ go run

# 새로운 모듈 생성
$ go mod init

# 필요한 의존성 설치/필요없는 의존성 삭제
$ go mod tidy

컨테이너 생성


리눅스의 Namespace/Cgroups 기능 이용
Go 기반으로 환경 구성 -> 독립된 환경에서 쉘 명령어 실행

CRI 런타임: Container runtime interface -> 컨테이너 런타임이 쿠버네티스의 kubelet과 어떻게 통신해야 하는지
OCI 런타임: Open container initiative -> 컨테이너 포맷과 런타임의 표준화

$ docker run images <CMD> <ARGS>
  
$ go run main.go run <CMD> <ARG>

namespace

호스트명, 프로세스ID, 프로세스 리스트 정보 isolation -> UTS, PID, Mount namespace

step1

package main

import (
	"fmt"
	"os"
)

// docker           run image <CMD> <ARG>
// go run main.go   run       <CMD> <ARG>

// Step1: 명령어 종류에 따른 함수 실행. "run" 명령어 전달 시 run 함수 실행.
func main() {
	switch os.Args[1] {
	case "run":
		run()
	default:
		os.Exit(1)
	}
}

func run() {
	// argument 전부 출력
	fmt.Printf("Running: %v\n", os.Args[2:])
}
$ go run . run ls -l
Running: [ls -l]

step2

package main

import (
	"fmt"
	"os"
	"os/exec"
)

// docker           run image <CMD> <ARG>
// go run main.go   run       <CMD> <ARG>

// Step2: 새로운 프로세스에서 명령어 실행  예) ls -l
func main() {
	switch os.Args[1] {
	case "run":
		run()
	default:
		os.Exit(1)
	}
}

func run() {
	fmt.Printf("Running: %v\n", os.Args[2:])
	cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	cmd.Run()
}
$ go run . run pwd
Running: [pwd]
/home/ubuntu/projects/box

step3

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

// docker           run image <CMD> <ARG>
// go run main.go   run       <CMD> <ARG>

// Step3: 새로운 UTS 설정 추가. Clone flag 추가 NEW UTS namespace. hostname 수동 변경 실습.
// 실습
// $ go run . run /bin/sh
// $ hostname box

func main() {
	switch os.Args[1] {
	case "run":
		run()
	default:
		os.Exit(1)
	}
}

func run() {
	fmt.Printf("Running: %v\n", os.Args[2:])
	cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS,
	}

	must(cmd.Run())
}

// 에러 발생시 확인후 종료하는 함수
func must(err error) {
	if err != nil {
		panic(err)
	}
}
# bash 셀로 컨테이너 진입해서 host명을 바꿈
$ go run . run /bin/bash
Running: [/bin/bash]

$ hostname
ip-{ip}

$ hostname container
$ hostname
container

# 다른 쉘에서 확인하면 별도의 namespace이기 때문에 container host가 잡히지 않음
$ hostname
ip-{ip}

step4

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

// docker           run image <CMD> <ARG>
// go run main.go   run       <CMD> <ARG>

// Step4: 컨테이너 환경 시작시 호스트명을 container로 변경.
// $ go run . run /bin/sh
// $ hostname

func main() {
	switch os.Args[1] {
	case "run":
		run()
	case "child":
		child()
	default:
		os.Exit(1)
	}
}

func run() {
	fmt.Printf("Running: %v\n", os.Args[2:])
	// cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS,
	}

	must(cmd.Run())
}

func child() {
	fmt.Printf("Running child: %v\n", os.Args[2:])

	// hostname 설정
	syscall.Sethostname([]byte("container"))

	cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	must(cmd.Run())
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
# hostname 변경된것을 확인
$ go run .run /bin/bash
Running: [/bin/bash]
Running child: [/bin/bash]
root@container

step5

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

// docker            run image <CMD> <ARG>
// go run main.go   run       <CMD> <ARG>

// Step5: 컨테이너 환경에서 ps명령 실행 시 제한된 프로세스 정보만 조회. 루트 파일 시스템 변경.
//        실습으로 ps, cat /os-release 명령 실행.

func main() {
	switch os.Args[1] {
	case "run":
		run()
	case "child":
		child()
	default:
		os.Exit(1)
	}
}

func run() {
	fmt.Printf("Running: %v as %d\n", os.Args[2:], os.Getpid())
	cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
	}

	must(cmd.Run())
}

func child() {
	fmt.Printf("Running child: %v as %d\n", os.Args[2:], os.Getpid())

	// hostname 설정
	syscall.Sethostname([]byte("container"))

	/* 루트 파일시스템 다운로드
	# https://github.com/tianon/docker-brew-ubuntu-core/raw/88ba31584652db8b96a29849ea2809d99ce3cc31/focal/ubuntu-focal-oci-amd64-root.tar.gz
	# mkdir /tmp/ubuntu
	# tar zxf ubuntu-focal-oci-amd64-root.tar.gz -C /tmp/ubuntu
	*/
	// 루트 파일시스템 변경
	// syscall.Chroot("/tmp/ubuntu")
	// syscall.Chdir("/")
	// syscall.Mount("proc", "proc", "proc", 0, "")
	// defer syscall.Unmount("proc", 0)

	cmd := exec.Command(os.Args[2], os.Args[3:]...)
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	must(cmd.Run())

}

func must(err error) {
	if err != nil {
		panic(err)
	}
}
$ go run . run /bin/bash
Running: [/bin/bash] as 869604
Running child: [/bin/bash] as 1

$ echo $$
4

$ ps aux | head -5
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  1.1  22212 11008 ?        Ss   Sep04   0:10 /usr/lib/systemd/systemd --sys

# 격리되지 않았음 .루트 파일시스템 다운로드 필요(컨테이너 내부)
$ wget {github 파일시스템}

$ mkdir /tmp/ubuntu

$ tar zxf ubuntu-focal-oci-amd64-root.tar.gz -C /tmp/ubuntu/

$ ls -l /tmp/ubuntu
total 60
lrwxrwxrwx  1 root root    7 Apr  5  2022 bin -> usr/bin
drwxr-xr-x  2 root root 4096 Apr 15  2020 boot
drwxr-xr-x  2 root root 4096 Apr  5  2022 dev
drwxr-xr-x 30 root root 4096 Apr  5  2022 etc
drwxr-xr-x  2 root root 4096 Apr 15  2020 home

# 이제, 위의 go 코드에서 루트 파일시스템 변경코드 주석해제

# 컨테이너 탈출
$ exit

$ go run . run /bin/bash

$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.1 1225424 1920 ?        Sl   10:20   0:00 /proc/self/exe child /bin/bash

  • run 명령어를 통해 run함수 실행
  • 새로운 프로세스에서 명령어 실행
  • 새로운 UTS 설정 추가해 hostname 변경
  • 컨테이너 환경 시작시 호스트명을 container로 변경
  • 컨테이너 환경에서 ps명령 실행시 제한된 프로세스 정보만 조회. 루트파일 시스템 변경

0개의 댓글