자체 Kubernetes 구축 - 파트 5 - 노드 명령

이전 게시물에서 우리는 노드 이미지를 빌드하고 에이전트를 작성했습니다. 모두 docker build 및 run을 사용하여 셸에서 명령을 실행하여 수행되었지만 전체 프로세스는 프로그램에 의해 자동화되어야 합니다. 명령에 노드를 생성하고 나열하고 삭제합니다.


시작하기 전에 코드에서 몇 가지 사소한 수정 사항이 있었습니다. 노드에서 동일한 이름을 사용할 것이기 때문에 cmd/pod.go에서 명령 변수의 이름을 바꾸고 에이전트 포드 경로를 포드로 변경하고 REST에서 BP로 포드가 아닌 포드로 변경합니다. GenerateNewID 기능을 pkg/util.go로 옮겼습니다. Dockerfile에 큰 변화가 있었습니다. 로컬에서 노드 이미지를 빌드하기 위해 명령을 실행하는 환경에 의존하고 있으므로 기본 프로그램, 에이전트 및 노드 이미지를 빌드하기 위해 Makefile을 만들었습니다.
메이크파일:

default: build

build: build-agent build-node-image
    go build -o bin/main main.go

build-node-image: 
    sudo docker build -t own-kube-node .

build-agent: 
    go build -o bin/agent pkg/agent/agent.go


결과적으로 이미지를 빌드할 때 이미 빌드된 에이전트의 바이너리에 의존할 수 있으므로 Dockerfile에서 빌드를 제거했습니다.

# Dockerfile for node image
FROM ubuntu

WORKDIR /agent

RUN apt-get update && apt-get install -y wget containerd

COPY bin/agent .

EXPOSE 10250

ENTRYPOINT [ "./agent" ]



노드 기능부터 시작하겠습니다. 포드와 마찬가지로 pkg/node에 노드 기능과 관련된 파일이 있습니다.pkg/node/constraints.go에서 시작하여 작업에 대한 몇 가지 제약 조건을 정의합니다.

package node

import "github.com/jonatan5524/own-kubernetes/pkg/agent/api"

const (
    NODE_NAME          = "node"
    NODE_IMAGE         = "own-kube-node"
    NODE_PORT          = api.PORT + "/tcp"
    NODE_PORT_HOST_IP  = "0.0.0.0"
    MEMORY_LIMIT       = 2.9e+9 // 2900MB
    CPU_LIMIT          = 2
    NODE_HOST_MIN_PORT = 10250
    NODE_HOST_MAX_PORT = 10300
)


모든 제약 조건은 다음 파일에서 사용되며 모든 값은 docker 컨테이너 노드를 실행할 때 마지막 기사에서 가져옵니다.pkg/node.go에서 Node 구조체를 정의합니다.

type Node struct {
    Id   string
    Port string
}


Id - 노드에 대해 생성된 ID
포트 - 노드에 사용되지 않는 포트 생성
이 파일에서 docker sdk을 사용하여 새 노드를 생성하는 함수도 정의합니다.

func NewNode(cli *client.Client, ctx context.Context) (*Node, error) {
    exists, err := isNodeImageExists(cli, ctx)
    if err != nil {
        return nil, err
    } else if exists == false {
        return nil, fmt.Errorf("node image: %s not exists locally, need to build the image", NODE_IMAGE)
    }

    id := pkg.GenerateNewID(NODE_NAME)
    config := &container.Config{
        Image: NODE_IMAGE,
    }

    hostConfig := &container.HostConfig{
        PortBindings: nat.PortMap{
            nat.Port(fmt.Sprintf("%s/tcp", api.PORT)): []nat.PortBinding{
                {
                    HostIP:   NODE_PORT_HOST_IP,
                    HostPort: "0",
                },
            },
        },
        Resources: container.Resources{
            Memory:    MEMORY_LIMIT,
            CPUShares: CPU_LIMIT,
        },
        Privileged: true,
    }

    _, err = cli.ContainerCreate(ctx, config, hostConfig, nil, nil, id)
    if err != nil {
        return nil, err
    }

    return &Node{Id: id}, nil
}


먼저 노드 이미지가 있는지 확인한 다음 ID를 생성하고 컨테이너에 대한 일부 구성을 만듭니다.
  • 노드 이미지
  • 컨테이너 내부의 포트(에이전트 API)에서 컨테이너 외부로의 포트 포워딩. Linux는 서비스가 0 포트를 할당할 때 임의의 열린 포트를 할당하기 때문에 HostPort는 0입니다.
  • 메모리 제한
  • CPU 제한
  • 특권
    모든 구성은 이전 기사에서 실행한 명령과 동일합니다.

  • sudo docker run -it --memory="2900MB" --cpus="2" -p 10250:10250 --privileged --name test --rm containerd_test
    


    그런 다음 구성 및 생성된 ID로 컨테이너를 만들고 새 노드 구조체를 반환합니다.isNodeImageExists 함수 구현:

    func isNodeImageExists(cli *client.Client, ctx context.Context) (bool, error) {
        images, err := cli.ImageList(ctx, types.ImageListOptions{})
        if err != nil {
            return false, err
        }
    
        for _, image := range images {
            if strings.Contains(image.RepoTags[0], NODE_IMAGE) {
                return true, nil
            }
        }
    
        return false, nil
    }
    


    기능은 기존 이미지를 나열하고 각 이미지를 살펴보고 해당 RepoTag에 다음이 포함되어 있는지 확인합니다own-kube-node.
    pkg/node/service.go 로 이동해 보겠습니다. 첫 번째로 수행할 함수는 도커 클라이언트와 컨텍스트를 초기화한 initDockerConnection 입니다.

    func initDockerConnection() (*client.Client, context.Context, error) {
        ctx := context.Background()
        cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
        if err != nil {
            return nil, nil, err
        }
    
        return cli, ctx, nil
    }
    


    다음은 새 노드를 만들고 컨테이너를 시작하는 NewNodeAndRun 함수입니다.

    func NewNodeAndRun() (*Node, error) {
        cli, ctx, err := initDockerConnection()
        if err != nil {
            return nil, err
        }
        defer cli.Close()
    
        node, err := NewNode(cli, ctx)
        if err != nil {
            return nil, err
        }
    
        if err := cli.ContainerStart(ctx, node.Id, types.ContainerStartOptions{}); err != nil {
            return nil, err
        }
    
        log.Printf("node created: %s\n", node.Id)
        log.Printf("starting node\n")
    
        container, err := cli.ContainerInspect(ctx, node.Id)
        if err != nil {
            return nil, err
        }
    
        // get linux generated port
        node.Port = container.NetworkSettings.Ports[nat.Port(fmt.Sprintf("%s/tcp", api.PORT))][0].HostPort
    
        log.Printf("node assign port: %s\n", node.Port)
    
        return node, nil
    }
    


    이 함수에서 볼 수 있듯이 docker 데몬에 대한 연결을 초기화하고 새 노드와 컨테이너를 만들고 컨테이너를 시작한 다음 포트 0을 할당했기 때문에 시스템에서 할당된 포트를 검색해야 합니다.

    다음 함수는 ListRunningNodes입니다.

    func ListRunningNodes() ([]string, error) {
        cli, ctx, err := initDockerConnection()
        if err != nil {
            return []string{}, err
        }
        defer cli.Close()
    
        runningNodes := []string{}
    
        filter := filters.NewArgs(filters.KeyValuePair{Key: "ancestor", Value: NODE_IMAGE})
        containers, err := cli.ContainerList(ctx, types.ContainerListOptions{Filters: filter})
        if err != nil {
            return runningNodes, err
        }
    
        for _, container := range containers {
            runningNodes = append(runningNodes, container.Names[0])
        }
    
        return runningNodes, nil
    }
    


    함수는 연결을 초기화하고 필터링할 이미지를 의미하는 ContainerList 필터와 함께 ancestor 메서드를 호출한 다음 각 컨테이너를 실행하여 이름 목록을 만듭니다.

    마지막 함수는 단순히 컨테이너를 중지하고 제거하는 KillNode 함수입니다.

    func KillNode(name string) (string, error) {
        cli, ctx, err := initDockerConnection()
        if err != nil {
            return "", err
        }
        defer cli.Close()
    
        if err := cli.ContainerStop(ctx, name, nil); err != nil {
            return "", err
        }
    
        removeOptions := types.ContainerRemoveOptions{
            RemoveVolumes: true,
            Force:         true,
        }
    
        if err := cli.ContainerRemove(ctx, name, removeOptions); err != nil {
            return "", err
        }
    
        return name, nil
    }
    


    우리가 완료해야 하는 마지막 부분은 cmd/node.go 그 구조는 cmd/pod.go 와 동일합니다. 이 문서가 너무 길기 때문에 여기에 이 ​​파일을 추가하지 않을 것입니다. 소스 코드에서 볼 수 있습니다.

    터미널에서 모든 것이 작동하는 것을 볼 수 있습니다.

    ❯ make
    go build -o bin/agent pkg/agent/agent.go
    sudo docker build -t own-kube-node .
    ...
    ❯ sudo ./bin/main node create
    2022/10/12 19:18:26 node created: node-7daf80ec-0a46-4977-952c-66deb81e32f7
    2022/10/12 19:18:26 starting node
    2022/10/12 19:18:26 node assign port: 49167
    ❯ sudo docker ps
    CONTAINER ID   IMAGE           COMMAND     CREATED         STATUS         PORTS                      NAMES
    2066e8e0b933   own-kube-node   "./agent"   5 seconds ago   Up 4 seconds   0.0.0.0:49167->10250/tcp   node-7daf80ec-0a46-4977-952c-66deb81e32f
    ❯ curl -X POST localhost:49167/pods -H 'Content-Type: application/json' -d '{"name": "redis", "image registry": "docker.io/library/redis:alpine"}'
    {"image registry":"docker.io/library/redis:alpine","name":"redis-a1cfce57-5db9-4894-a8cf-c277444e1b0c"}
    



    이제 API 호출을 사용하여 명령으로 생성된 노드가 있습니다. 이 노드는 노드 내부에 포드를 생성할 수도 있습니다!

    항상 그렇듯이 소스 코드는 here , 변경 사항은 pkg/node , Dockerfile , Makefile , cmd/node 에서 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기