Minikube에 Application Deploy 방법

|

Minikube에 Application Deploy 방법

Service

아래와 같이 샘플 Service를 생성할 수 있습니다.

$ kubectl create deployment hello-minikube --image=kicbase/echo-server:1.0

deployment.apps/hello-minikube created
$ kubectl expose deployment hello-minikube --type=NodePort --port=8080

service/hello-minikube exposed

잠시후 다음 명령어를 통해 위에서 생성한 hello-minikube 서비스 상태를 확인할 수 있습니다.

$ kubectl get services hello-minikube

NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-minikube   NodePort   10.105.140.39           8080:30634/TCP   59s
</pre>

여기서 실제 브라우저로 동작을 확인하고 싶으면 아래 명령어를 실행하면 됩니다.

$ minikube service hello-minikube

|-----------|----------------|-------------|---------------------------|
| NAMESPACE |      NAME      | TARGET PORT |            URL            |
|-----------|----------------|-------------|---------------------------|
| default   | hello-minikube |        8080 | http://192.168.49.2:30634 |
|-----------|----------------|-------------|---------------------------|
🏃  Starting tunnel for service hello-minikube.
|-----------|----------------|-------------|------------------------|
| NAMESPACE |      NAME      | TARGET PORT |          URL           |
|-----------|----------------|-------------|------------------------|
| default   | hello-minikube |             | http://127.0.0.1:53680 |
|-----------|----------------|-------------|------------------------|
🎉  Opening service default/hello-minikube in default browser...
❗  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.
그러면 자동으로 브라우저가 열리며, `hello-minikube` 페이지를 볼 수 있습니다. ### Port forwarding
$ kubectl port-forward service/hello-minikube 7080:8080

Forwarding from 127.0.0.1:7080 -> 8080
Forwarding from [::1]:7080 -> 8080
이 경우에는 http://localhost:7080/ 주소에서 위에서 만든 웹페이지를 확인할 수 있습니다. ## LoadBalancer
$ kubectl create deployment balanced --image=kicbase/echo-server:1.0

deployment.apps/balanced created
$ kubectl expose deployment balanced --type=LoadBalancer --port=8080

service/balanced exposed
다른 터미널 창을 열고 아래 명령어를 이용해서 `tunnel`을 실행해서 `balanced` 배포 대상의 routable IP를 생성해줍니다.
$ minikube tunnel

✅  Tunnel successfully started
📌  NOTE: Please do not close this terminal as this process must stay alive for the tunnel to be accessible ...
🏃  Starting tunnel for service balanced.
$ kubectl get services balanced

NAME       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
balanced   LoadBalancer   10.99.117.109   127.0.0.1     8080:31198/TCP   83s

# 만약 위에서 `tunnel`을 실행하지 않았다면 아래와 같이 출력됩니다.
$ kubectl get services balanced

NAME       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
balanced   LoadBalancer   10.99.117.109        8080:31198/TCP   2m41s
</pre>

그리고 브라우저에서 `http://127.0.0.1:8080`에 접속해서 위의 `EXTERNAL-IP`가 잘 작동하는지 확인할 수 있습니다.

## Ingress

Ingress를 사용하기 위해서는 아래 명령어로 minikube에 addon을 설치해줍니다.

$ minikube addons enable ingress

💡  ingress is an addon maintained by Kubernetes. For any concerns contact minikube on GitHub.
You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS
💡  After the addon is enabled, please run "minikube tunnel" and your ingress resources would be available at "127.0.0.1"
    ▪ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407
    ▪ Using image registry.k8s.io/ingress-nginx/controller:v1.8.1
    ▪ Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407
🔎  Verifying ingress addon...
🌟  The 'ingress' addon is enabled
$ kubectl apply -f https://storage.googleapis.com/minikube-site-examples/ingress-example.yaml

pod/foo-app created
service/foo-service created
pod/bar-app created
service/bar-service created
ingress.networking.k8s.io/example-ingress created
#### ingress-example.yaml 위 파일을 브라우저 등으로 다운로드해보면 아래와 같은 내용이 입력되어 있는 것을 확인할 수 있습니다.
kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
    - name: foo-app
      image: 'kicbase/echo-server:1.0'
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
    - port: 8080
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
    - name: bar-app
      image: 'kicbase/echo-server:1.0'
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
    - port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
    - http:
        paths:
          - pathType: Prefix
            path: /foo
            backend:
              service:
                name: foo-service
                port:
                  number: 8080
          - pathType: Prefix
            path: /bar
            backend:
              service:
                name: bar-service
                port:
                  number: 8080
---
### Ingress 확인
$ kubectl get ingress

NAME              CLASS   HOSTS   ADDRESS        PORTS   AGE
example-ingress   nginx   *       192.168.49.2   80      48s
다시 새로운 터미널을 열고 `minikube tunnel` 명령어를 이용해서 tunnel을 실행한다음, 브라우저에서는 `http://127.0.0.1/foo` 또는 `http://127.0.0.1/bar`로 ingress 및 LoadBalancer가 실행되고 있는 것을 확인할 수 있습니다. ## Reference - https://minikube.sigs.k8s.io/docs/start/

Minikube 사용방법

|

Minikube 사용방법

명령어 설명
minikube start minikube 가상머신 실행
minikube stop minikube 가상머신 정지
minikube delete 가상머신 제거
minikube status 상태 확인
minikube ip IP 주소 확인
minikube ssh ssh 접속
minikube addons list 애드온 목록 조회
minikube dashboard 브라우저로 minikube 대시보드 접속

대시보드 url 확인

$ minikube dashboard  --url

🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
http://127.0.0.1:53502/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

MAC OS에 minikube 설치하는 방법

|

MAC OS에 minikube 설치

다음과 같이 brew를 이용해서 설치하면 됩니다.

$ brew install minikube

Running `brew update --auto-update`...
==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:905b0c3896164ae8067a22fff2fd0b80b16d3c8bb72441403eedf69da71ec717
################################################################################################################# 100.0%
==> Pouring portable-ruby-2.6.10_1.arm64_big_sur.bottle.tar.gz
==> Homebrew collects anonymous analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics have been recorded yet (nor will be during this `brew` run).

Installing from the API is now the default behaviour!
You can save space and time by running:
  brew untap homebrew/core
  brew untap homebrew/cask
==> Downloading https://formulae.brew.sh/api/formula.jws.json
################################################################################################################# 100.0%
==> Downloading https://formulae.brew.sh/api/cask.jws.json
################################################################################################################# 100.0%
==> Fetching dependencies for minikube: kubernetes-cli
==> Fetching kubernetes-cli

...


==> `brew cleanup` has not been run in the last 30 days, running now...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Removing: /Users/snowdeer/Library/Caches/Homebrew/nvm--0.39.3... (47.2KB)
Removing: /Users/snowdeer/Library/Caches/Homebrew/nvm_bottle_manifest--0.39.3... (1.7KB)
Removing: /Users/snowdeer/Library/Logs/Homebrew/nvm... (64B)
==> Caveats
==> minikube
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions

만약 brew를 사용하지 않으면 다음 명령어를 이용해서 minikube를 설치할 수 있습니다.

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64
$ sudo install minikube-darwin-arm64 /usr/local/bin/minikube

설치 확인

$ minikube version

minikube version: v1.31.1
commit: fd3f3801765d093a485d255043149f92ec0a695f

minikube 실행

$ minikube start

😄  minikube v1.31.1 on Darwin 12.4 (arm64)
✨  Automatically selected the docker driver
📌  Using Docker Desktop driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.27.3 preload ...
    > preloaded-images-k8s-v18-v1...:  327.72 MiB / 327.72 MiB  100.00% 1.65 Mi
    > gcr.io/k8s-minikube/kicbase...:  404.50 MiB / 404.50 MiB  100.00% 1.76 Mi
🔥  Creating docker container (CPUs=2, Memory=1988MB) ...
🐳  Preparing Kubernetes v1.27.3 on Docker 24.0.4 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔗  Configuring bridge CNI (Container Networking Interface) ...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying Kubernetes components...
🌟  Enabled addons: storage-provisioner, default-storageclass
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

kubectl 명령어로 pods 정보 확인

$ kubectl get po -A

NAMESPACE              NAME                                         READY   STATUS    RESTARTS        AGE
kube-system            coredns-5d78c9869d-bmsck                     1/1     Running   0               3m28s
kube-system            etcd-minikube                                1/1     Running   0               3m41s
kube-system            kube-apiserver-minikube                      1/1     Running   0               3m41s
kube-system            kube-controller-manager-minikube             1/1     Running   0               3m41s
kube-system            kube-proxy-qcp9m                             1/1     Running   0               3m28s
kube-system            kube-scheduler-minikube                      1/1     Running   0               3m41s
kube-system            storage-provisioner                          1/1     Running   1 (2m57s ago)   3m39s
kubernetes-dashboard   dashboard-metrics-scraper-5dd9cbfd69-nvbmj   1/1     Running   0               113s
kubernetes-dashboard   kubernetes-dashboard-5c5cfc8747-8g7mn        1/1     Running   0               113s

Reference

  • https://minikube.sigs.k8s.io/docs/start/

Pixi.js Viewport 예제코드

|

Pixi.js Viewport 예제코드

PixiView.vue

<template>
  <div id="container">
    <canvas id="pixi-canvas"></canvas>
  </div>
</template>

<script>
import "./js/pixi-app";
import { PixiApp } from "./js/pixi-app";
export default {
  mounted() {
    this.createPixiApp();
  },
  methods: {
    createPixiApp() {
      const app = new PixiApp(800, 600);
      const canvas = document.getElementById("pixi-canvas");
      canvas.appendChild(app.view);
      console.log(`window size(${window.innerWidth}, ${window.innerHeight})`);
      console.log(`app size(${app.view.width}, ${app.view.height})`);
    },
  },
};
</script>

<style>
#container {
  display: block;
  width: 100vw;
  height: 100vh;
  background: whitesmoke;
}
#pixi-canvas {
  display: block;
  margin: 5%;
}
</style>

js/pixi-app.js

import * as PIXI from "pixi.js-legacy";
import { Viewport } from "pixi-viewport";

class PixiApp extends PIXI.Application {
  constructor(width, height) {
    super({
      width: width,
      height: height,
      backgroundColor: 0xffebee,
      antialias: true,
    });

    const screenWidth = 800;
    const screenHeight = 600;
    const worldWidth = screenWidth * 2;
    const worldHeight = screenHeight * 2;

    const viewport = new Viewport({
      screenWidth: screenWidth,
      screenHeight: screenHeight,
      worldWidth: worldWidth,
      worldHeight: worldHeight,

      interaction: this.renderer.plugins.interaction,
    });

    this.stage.addChild(viewport);

    const grid = this.#createGrid();
    viewport.addChild(grid);

    const boundary = this.#createBoundary(worldWidth, worldHeight);
    viewport.addChild(boundary);

    this.#registerViewportEventHandler(viewport);

    console.log(viewport);
    console.log(`Screen width: ${viewport.screenWidth}`);
    console.log(`World width: ${viewport.worldWidth}`); // World height, in pixels
    console.log(
      `Screen width in World pixels width(${viewport.screenWidthInWorldPixels})`
    ); // Get how many world pixels fit in screen's width
    console.log(`Screen World width(${viewport.screenWorldWidth})`); // World width in screen coordinates
    console.log(`World Screen width(${viewport.worldScreenWidth})`); // Screen width in world coordinates
    console.log(`Viewport Corner`, viewport.corner); // Screen width in world coordinates
    console.log(`Viewport Center`, viewport.center); // Screen width in world coordinates
  }

  #registerViewportEventHandler(viewport) {
    const screenWidth = 800;
    const screenHeight = 600;

    viewport
      .drag()
      .pinch()
      .wheel()
      .decelerate()
      .clamp({
        direction: "all",
        underflow: "center",
      })
      .clampZoom({
        minWidth: screenWidth,
        minHeight: screenHeight,
        maxWidth: screenWidth * 3,
        maxHeight: screenHeight * 3,
      })
      .clampZoom({
        minScale: 0.5,
        maxScale: 2,
      })
      .fit();

    viewport.on("pointerup", (e) => {
      const x = e.data.global.x;
      const y = e.data.global.y;
      console.log(`point UP (${x}, ${y})`); // Viewport 좌표
      console.log(`toScreen: `, viewport.toScreen(x, y));
      console.log(`toWorld: `, viewport.toWorld(x, y));
      console.log(`visibleBounds()`, viewport.getVisibleBounds());
    });
  }

  #createBoundary(width, height) {
    const container = new PIXI.Container();
    const boundary = new PIXI.Graphics();
    boundary.lineStyle(8, 0xff0000);
    boundary.drawRect(0, 0, width, height);
    container.addChild(boundary);

    return container;
  }

  #createGrid() {
    const container = new PIXI.Container();
    const origin = this.#createCrossPoint({
      x: 0,
      y: 0,
      color: 0xff0000,
      size: 50,
      fontsize: 14,
    });
    container.addChild(origin);

    for (let row = 0; row <= 20; row++) {
      for (let col = 0; col <= 20; col++) {
        if (row == 0 && col == 0) continue;

        const p = this.#createCrossPoint({
          x: col * 100,
          y: row * 100,
        });
        container.addChild(p);
      }
    }

    return container;
  }

  #createCrossPoint({
    x,
    y,
    color = 0x000000,
    size = 5,
    thickness = 1,
    fontsize = 8,
  }) {
    const container = new PIXI.Container();

    const grid = new PIXI.Graphics();
    grid.lineStyle(thickness, color);
    grid.moveTo(x - size, y);
    grid.lineTo(x + size, y);
    grid.moveTo(x, y - size);
    grid.lineTo(x, y + size);
    container.addChild(grid);

    const text = new PIXI.Text(`(${x},${y})`, {
      fontFamily: "Arial",
      fontSize: fontsize,
      fill: color,
    });
    text.position.set(x + 3, y + 3);
    container.addChild(text);

    return container;
  }
}

export { PixiApp };

실행화면

화면의 각 위치를 터치해보고, 화면을 panning으로 이동 후 각 위치를 터치, Zoom 등으로 화면을 확대/축소하면서 각 위치를 터치하면서 좌표계를 확인해볼 수 있습니다.

image

각 이벤트를 거쳐 전달되는 좌표값은 Viewport 상의 좌표이며, 이를 World 좌표로 변환하면 각 오브젝트가 그려진 Canvas 상의 좌표라고 볼 수 있습니다.

Pixi.js Viewport 상에서의 Event

|

Pixi.js Viewport 상에서의 Event

pixi-viewport라는 유틸리티를 이용해서 구현한 예제입니다.

package.json

아래와 같이 모듈을 추가해주면 됩니다.

...

"dependencies": {
  "core-js": "^3.8.3",
  "pixi-viewport": "^4.34.4",
  "pixi.js-legacy": "^6.3.0",
  "vue": "^3.2.13"
},

...

PixiView.vue

<template>
  <div id="container">
    <div id="pixi-canvas"></div>
  </div>
</template>

<script>
import "./js/pixi-app";
import { PixiApp } from "./js/pixi-app";
export default {
  mounted() {
    this.createPixiApp();
  },
  methods: {
    createPixiApp() {
      const app = new PixiApp(800, 800);
      const canvas = document.getElementById("pixi-canvas");
      canvas.appendChild(app.view);
      console.log(`window size(${window.innerWidth}, ${window.innerHeight})`);
      console.log(`app size(${app.view.width}, ${app.view.height})`);
    },
  },
};
</script>

<style>
#container {
  display: block;
  width: 100vw;
  height: 100vh;
  background: whitesmoke;
}
#pixi-canvas {
  display: block;
  margin: 20px;
}
</style>

pixp-app.js

import * as PIXI from "pixi.js-legacy";
import { Viewport } from "pixi-viewport";

class PixiApp extends PIXI.Application {
  constructor(width, height) {
    super({
      width: width,
      height: height,
      backgroundColor: 0xffebee,
      antialias: true,
    });

    const viewport = new Viewport({
      screenWidth: width,
      screenHeight: height,
      worldWidth: width * 2,
      worldHeight: height * 2,

      interaction: this.renderer.plugins.interaction, // the interaction module is important for wheel to work properly when renderer.view is placed or scaled
    });

    this.stage.addChild(viewport);

    const rect = this.#createRect(100, 100);
    viewport.addChild(rect);
    rect.interactive = true;
    rect.on("pointerdown", (e) => {
      console.log(`rect DOWN (${e.data.global.x}, ${e.data.global.y})`); // Viewport 좌표
    });

    const grid = this.#createGrid();
    viewport.addChild(grid);

    viewport.drag().pinch().wheel().decelerate();

    viewport.on("pointerdown", (e) => {
      console.log(`point DOWN (${e.data.global.x}, ${e.data.global.y})`); // Viewport 좌표
    });
  }

  #createRect(centerX, centerY, width = 100, height = 100) {
    const rect = new PIXI.Graphics();
    rect.beginFill(0xff8080);
    rect.drawRect(0, 0, width, height);
    rect.pivot.set(width / 2, height / 2);
    rect.position.set(centerX, centerY);
    rect.endFill();

    return rect;
  }

  #createGrid() {
    const grid = new PIXI.Graphics();
    grid.lineStyle(2, 0xf06292);
    grid.moveTo(-2000, 0);
    grid.lineTo(2000, 0);
    grid.moveTo(0, -2000);
    grid.lineTo(0, 2000);
    grid.lineStyle(1, 0x000000);
    for (let i = 1; i < 5; i++) {
      grid.moveTo(i * 400, -2000);
      grid.lineTo(i * 400, 2000);
      grid.moveTo(-2000, i * 400);
      grid.lineTo(2000, i * 400);
    }

    return grid;
  }
}

export { PixiApp };

실행화면

image