Go๋ ํ์ฌ ์ฌ๋๋ค์ด Kubernetes์ฉ ๋ช
๋ น๋ฌธ์ ์์ฑํ๊ธฐ ์ํด ์ ํํ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ๋
์ ํ๊ณ ์์ต๋๋ค. ์ด์ ๋ํ ๊ฐ๊ด์ ์ธ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Go์๋ ์ฐ์ฐ์ ๊ฐ๋ฐ์ ์ํ ๊ฐ๋ ฅํ ํ๋ ์์ํฌ๊ฐ ์์ต๋๋ค.
์ฐ์ฐ์ SDK . - Docker ๋ฐ Kubernetes์ ๊ฐ์ ํ๋๋ฅผ ๋ฐ๊พธ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ Go๋ก ์์ฑ๋์์ต๋๋ค. Go์์ ์ฐ์ฐ์๋ฅผ ์์ฑํ๋ค๋ ๊ฒ์ ์ํ๊ณ์ ๋์ผํ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋ค๋ ์๋ฏธ์ ๋๋ค.
- ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋์ ์์ ์ ์ํ ๊ณ ์ฑ๋ฅ Go ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๊ฐ๋จํ ๋๊ตฌ์ ๋๋ค.
NB: ๊ทธ๋ฐ๋ฐ Go์์ ์์ ๋ง์ ๋ฌธ์ฅ์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ฆฌ๊ฒ ์ต๋๋ค.
ํ์ง๋ง ์๊ฐ์ด ๋ถ์กฑํ๊ฑฐ๋ ๊ฐ๋จํ ๋งํด์ ๋๊ธฐ๊ฐ ๋ถ์กฑํ์ฌ ๋ฐ๋์ ๋ฐฐ์ฐ์ง ๋ชปํ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ์ด ๊ธฐ์ฌ์์๋ ๊ฑฐ์ ๋ชจ๋ DevOps ์์ง๋์ด๊ฐ ์๊ณ ์๋ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ธ์ด ์ค ํ๋๋ฅผ ์ฌ์ฉํ์ฌ ์ข์ ๋ฌธ์ฅ์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์๋ฅผ ์ ๊ณตํฉ๋๋ค. Python.
๋ง๋๋ณด์ธ์: ๋ณต์ฌ๊ธฐ - ๋ณต์ฌ๊ธฐ ์ด์์!
์๋ฅผ ๋ค์ด, ์ ๋ค์์คํ์ด์ค๊ฐ ๋ํ๋ ๋ ๋๋ ๋ ์ํฐํฐ(ConfigMap ๋ฐ Secret) ์ค ํ๋๊ฐ ๋ณ๊ฒฝ๋ ๋ ConfigMap์ ๋ณต์ฌํ๋๋ก ์ค๊ณ๋ ๊ฐ๋จํ ๋ฌธ์ ๊ฐ๋ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณด์ธ์. ์ค์ฉ์ ์ธ ๊ด์ ์์ ์ฐ์ฐ์๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑ์ ๋๋์ผ๋ก ์ ๋ฐ์ดํธํ๊ฑฐ๋(ConfigMap ์ ๋ฐ์ดํธ๋ฅผ ํตํด) ๋น๋ฐ ๋ฐ์ดํฐ(์: Docker ๋ ์ง์คํธ๋ฆฌ ์์ ์ ์ํ ํค(๋ค์์คํ์ด์ค์ ๋น๋ฐ์ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ))๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์ ์ฉํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์, ์ข์ ์ด์์๋ ๋ฌด์์ ๊ฐ์ ธ์ผํฉ๋๊น?:
- ์ด์์์์ ์ํธ ์์ฉ์ ๋ค์์ ์ฌ์ฉํ์ฌ ์ํ๋ฉ๋๋ค.
์ฌ์ฉ์ ์ ์ ๋ฆฌ์์ค ์ ์ (์ดํ CRD๋ผ ์นญํจ) - ์ด์์๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด ๋ช ๋ น์ค ํ๋๊ทธ์ ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- Docker ์ปจํ ์ด๋ ๋ฐ Helm ์ฐจํธ์ ๋น๋๋ ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ(๋ง ๊ทธ๋๋ก ํ๋์ ๋ช ๋ น์ ์ฌ์ฉํ์ฌ) ์ฐ์ฐ์๋ฅผ Kubernetes ํด๋ฌ์คํฐ์ ์ค์นํ ์ ์๋๋ก ์ค๊ณ๋์์ต๋๋ค.
CRD
์ด์์๊ฐ ์ด๋ค ๋ฆฌ์์ค๋ฅผ ์ฐพ์์ผ ํ๋์ง, ์ด๋๋ฅผ ์ฐพ์์ผ ํ๋์ง ์๊ธฐ ์ํด์๋ ๊ทธ์ ๋ํ ๊ท์น์ ์ค์ ํด์ผ ํฉ๋๋ค. ๊ฐ ๊ท์น์ ๋จ์ผ CRD ๊ฐ์ฒด๋ก ํ์๋ฉ๋๋ค. ์ด CRD์๋ ์ด๋ค ํ๋๊ฐ ์์ด์ผ ํฉ๋๊น?
- ๋ฆฌ์์ค ์ ํ, ์ฐ๋ฆฌ๊ฐ ์ฐพ์ ๊ฒ์ ๋๋ค (ConfigMap ๋๋ Secret).
- ๋ค์์คํ์ด์ค ๋ชฉ๋ก, ๋ฆฌ์์ค๊ฐ ์์นํด์ผ ํ๋ ์์น์ ๋๋ค.
- ์ ํ์, ๋ค์์คํ์ด์ค์์ ๋ฆฌ์์ค๋ฅผ ๊ฒ์ํฉ๋๋ค.
CRD๋ฅผ ์ค๋ช ํด๋ณด์:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: copyrator.flant.com
spec:
group: flant.com
versions:
- name: v1
served: true
storage: true
scope: Namespaced
names:
plural: copyrators
singular: copyrator
kind: CopyratorRule
shortNames:
- copyr
validation:
openAPIV3Schema:
type: object
properties:
ruleType:
type: string
namespaces:
type: array
items:
type: string
selector:
type: string
๊ทธ๋ฌ๋ฉด ๋ฐ๋ก ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ๊ฐ๋จํ ๊ท์น โ ์ด๋ฆ์ด ์๋ ๋ค์์คํ์ด์ค์์ ๊ฒ์ํฉ๋๋ค. default
๋ค์๊ณผ ๊ฐ์ ๋ผ๋ฒจ์ด ์๋ ๋ชจ๋ ConfigMap copyrator: "true"
:
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: main-rule
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: default
์ค๋น๊ฐ ๋! ์ด์ ์ฐ๋ฆฌ๋ ๊ท์น์ ๋ํ ์ ๋ณด๋ฅผ ์ด๋ป๊ฒ๋ ์ป์ด์ผ ํฉ๋๋ค. ํด๋ฌ์คํฐ API ์๋ฒ์ ์ง์ ์์ฒญ์ ์์ฑํ์ง ์์ ๊ฒ์์ ์ฆ์ ์์ฝํ๊ฒ ์ต๋๋ค. ์ด๋ฅผ ์ํด ์ด๋ฏธ ๋ง๋ค์ด์ง Python ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
import kubernetes
from contextlib import suppress
CRD_GROUP = 'flant.com'
CRD_VERSION = 'v1'
CRD_PLURAL = 'copyrators'
def load_crd(namespace, name):
client = kubernetes.client.ApiClient()
custom_api = kubernetes.client.CustomObjectsApi(client)
with suppress(kubernetes.client.api_client.ApiException):
crd = custom_api.get_namespaced_custom_object(
CRD_GROUP,
CRD_VERSION,
namespace,
CRD_PLURAL,
name,
)
return {x: crd[x] for x in ('ruleType', 'selector', 'namespace')}
์ด ์ฝ๋๋ฅผ ์คํํ ๊ฒฐ๊ณผ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ต๋๋ค.
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}
ํ๋ฅญํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์ด์์์ ๋ํ ๊ท์น์ ์ป์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ฅ ์ค์ํ ๊ฒ์ ์ฐ๋ฆฌ๊ฐ Kubernetes ๋ฐฉ์์ด๋ผ๋ ๊ฒ์ ์ํํ๋ค๋ ๊ฒ์ ๋๋ค.
ํ๊ฒฝ ๋ณ์ ๋๋ ํ๋๊ทธ? ์ฐ๋ฆฌ๋ ๋ชจ๋ ๊ฒ์ ๊ฐ์ ธ๊ฐ๋๋ค!
์ฃผ์ ์ด์์ ๊ตฌ์ฑ์ผ๋ก ๋์ด ๊ฐ์๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ๋ ๋ฐ๋ ๋ ๊ฐ์ง ๊ธฐ๋ณธ ์ ๊ทผ ๋ฐฉ์์ด ์์ต๋๋ค.
- ๋ช ๋ น์ค ์ต์ ์ ์ฌ์ฉํ์ญ์์ค.
- ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํ์ญ์์ค.
๋ช
๋ น์ค ์ต์
์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ์ ํ ์ง์ ๋ฐ ๊ฒ์ฆ์ ํตํด ์ค์ ์ ๋ณด๋ค ์ ์ฐํ๊ฒ ์ฝ์ ์ ์์ต๋๋ค. Python์ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ๋ชจ๋์ด ์์ต๋๋ค. argparser
, ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ๊ฒ์
๋๋ค. ํด๋น ๊ธฐ๋ฅ์ ๋ํ ์์ธํ ๋ด์ฉ๊ณผ ์๋ ๋ค์์์ ํ์ธํ ์ ์์ต๋๋ค.
์ฐ๋ฆฌ์ ๊ฒฝ์ฐ ๋ช ๋ น์ค ํ๋๊ทธ ์ฝ๊ธฐ๋ฅผ ์ค์ ํ๋ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
parser = ArgumentParser(
description='Copyrator - copy operator.',
prog='copyrator'
)
parser.add_argument(
'--namespace',
type=str,
default=getenv('NAMESPACE', 'default'),
help='Operator Namespace'
)
parser.add_argument(
'--rule-name',
type=str,
default=getenv('RULE_NAME', 'main-rule'),
help='CRD Name'
)
args = parser.parse_args()
๋ฐ๋ฉด, ์ฟ ๋ฒ๋คํฐ์ค์์๋ ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ฉด ํฌ๋์ ๋ํ ์๋น์ค ์ ๋ณด๋ฅผ ์ปจํ ์ด๋ ๋ด๋ถ๋ก ์ฝ๊ฒ ์ ๋ฌํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ๋ค์ ๊ตฌ์ฑ์ ์ฌ์ฉํ์ฌ Pod๊ฐ ์คํ ์ค์ธ ๋ค์์คํ์ด์ค์ ๋ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์์ต๋๋ค.
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
์ฐ์ฐ์ ๋ ผ๋ฆฌ
ConfigMap๊ณผ Secret์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ถ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๊ธฐ ์ํด ํน์ ๋งต์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ๊ฐ์ฒด๋ฅผ ์ถ์ ํ๊ณ ์์ฑํ๋ ๋ฐ ํ์ํ ๋ฉ์๋๊ฐ ๋ฌด์์ธ์ง ์ดํดํ ์ ์์ต๋๋ค.
LIST_TYPES_MAP = {
'configmap': 'list_namespaced_config_map',
'secret': 'list_namespaced_secret',
}
CREATE_TYPES_MAP = {
'configmap': 'create_namespaced_config_map',
'secret': 'create_namespaced_secret',
}
๋ค์์ผ๋ก API ์๋ฒ๋ก๋ถํฐ ์ด๋ฒคํธ๋ฅผ ์์ ํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํด๋ณด์:
def handle(specs):
kubernetes.config.load_incluster_config()
v1 = kubernetes.client.CoreV1Api()
# ะะพะปััะฐะตะผ ะผะตัะพะด ะดะปั ัะปะตะถะตะฝะธั ะทะฐ ะพะฑัะตะบัะฐะผะธ
method = getattr(v1, LIST_TYPES_MAP[specs['ruleType']])
func = partial(method, specs['namespace'])
w = kubernetes.watch.Watch()
for event in w.stream(func, _request_timeout=60):
handle_event(v1, specs, event)
์ด๋ฒคํธ๋ฅผ ์์ ํ ํ ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๊ธฐ๋ณธ ๋ ผ๋ฆฌ๋ก ์ด๋ํฉ๋๋ค.
# ะขะธะฟั ัะพะฑััะธะน, ะฝะฐ ะบะพัะพััะต ะฑัะดะตะผ ัะตะฐะณะธัะพะฒะฐัั
ALLOWED_EVENT_TYPES = {'ADDED', 'UPDATED'}
def handle_event(v1, specs, event):
if event['type'] not in ALLOWED_EVENT_TYPES:
return
object_ = event['object']
labels = object_['metadata'].get('labels', {})
# ะัะตะผ ัะพะฒะฟะฐะดะตะฝะธั ะฟะพ selector'ั
for key, value in specs['selector'].items():
if labels.get(key) != value:
return
# ะะพะปััะฐะตะผ ะฐะบัะธะฒะฝัะต namespace'ั
namespaces = map(
lambda x: x.metadata.name,
filter(
lambda x: x.status.phase == 'Active',
v1.list_namespace().items
)
)
for namespace in namespaces:
# ะัะธัะฐะตะผ ะผะตัะฐะดะฐะฝะฝัะต, ัััะฐะฝะฐะฒะปะธะฒะฐะตะผ namespace
object_['metadata'] = {
'labels': object_['metadata']['labels'],
'namespace': namespace,
'name': object_['metadata']['name'],
}
# ะัะทัะฒะฐะตะผ ะผะตัะพะด ัะพะทะดะฐะฝะธั/ะพะฑะฝะพะฒะปะตะฝะธั ะพะฑัะตะบัะฐ
methodcaller(
CREATE_TYPES_MAP[specs['ruleType']],
namespace,
object_
)(v1)
์ฃผ์ ๋
ผ๋ฆฌ๊ฐ ์ค๋น๋์์ต๋๋ค! ์ด์ ์ด ๋ชจ๋ ๊ฒ์ ํ๋์ Python ํจํค์ง๋ก ํจํค์งํด์ผ ํฉ๋๋ค. ํ์ผ์ ์ค๋นํฉ๋๋ค setup.py
, ๊ฑฐ๊ธฐ์ ํ๋ก์ ํธ์ ๋ํ ๋ฉํ ์ ๋ณด๋ฅผ ์์ฑํฉ๋๋ค.
from sys import version_info
from setuptools import find_packages, setup
if version_info[:2] < (3, 5):
raise RuntimeError(
'Unsupported python version %s.' % '.'.join(version_info)
)
_NAME = 'copyrator'
setup(
name=_NAME,
version='0.0.1',
packages=find_packages(),
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
author='Flant',
author_email='[email protected]',
include_package_data=True,
install_requires=[
'kubernetes==9.0.0',
],
entry_points={
'console_scripts': [
'{0} = {0}.cli:main'.format(_NAME),
]
}
)
NB: Python์ฉ kubernetes ํด๋ผ์ด์ธํธ์๋ ์์ฒด ๋ฒ์ ์ด ์์ต๋๋ค. ํด๋ผ์ด์ธํธ ๋ฒ์ ๊ณผ Kubernetes ๋ฒ์ ๊ฐ์ ํธํ์ฑ์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์์ ํ์ธํ ์ ์์ต๋๋ค.
์ด์ ์ฐ๋ฆฌ ํ๋ก์ ํธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
copyrator
โโโ copyrator
โ โโโ cli.py # ะะพะณะธะบะฐ ัะฐะฑะพัั ั ะบะพะผะฐะฝะดะฝะพะน ัััะพะบะพะน
โ โโโ constant.py # ะะพะฝััะฐะฝัั, ะบะพัะพััะต ะผั ะฟัะธะฒะพะดะธะปะธ ะฒััะต
โ โโโ load_crd.py # ะะพะณะธะบะฐ ะทะฐะณััะทะบะธ CRD
โ โโโ operator.py # ะัะฝะพะฒะฝะฐั ะปะพะณะธะบะฐ ัะฐะฑะพัั ะพะฟะตัะฐัะพัะฐ
โโโ setup.py # ะัะพัะผะปะตะฝะธะต ะฟะฐะบะตัะฐ
๋์ปค์ ํฌ๋ฆ
Dockerfile์ ๋๋ผ์ธ ์ ๋๋ก ๊ฐ๋จํฉ๋๋ค. ๊ธฐ๋ณธ python-alpine ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค. ๋ ๋์ ๋๊น์ง ์ต์ ํ๋ฅผ ์ฐ๊ธฐํด ๋ณด๊ฒ ์ต๋๋ค.
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
์ด์์๋ฅผ ์ํ ๋ฐฐํฌ๋ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
selector:
matchLabels:
name: {{ .Chart.Name }}
template:
metadata:
labels:
name: {{ .Chart.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: privaterepo.yourcompany.com/copyrator:latest
imagePullPolicy: Always
args: ["--rule-type", "main-rule"]
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
serviceAccountName: {{ .Chart.Name }}-acc
๋ง์ง๋ง์ผ๋ก ํ์ํ ๊ถํ์ด ์๋ ์ด์์์ ๋ํ ์ ์ ํ ์ญํ ์ ์์ฑํด์ผ ํฉ๋๋ค.
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Chart.Name }}-acc
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: {{ .Chart.Name }}
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: {{ .Chart.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Chart.Name }}
subjects:
- kind: ServiceAccount
name: {{ .Chart.Name }}
ํฉ๊ณ
๊ทธ๋์ ์ฐ๋ฆฌ๋ ๋๋ ค์์ด๋ ๋น๋, Go๋ฅผ ๋ฐฐ์ฐ์ง ์๊ณ ๋ Python์์ Kubernetes์ฉ ์ฐ์ฐ์๋ฅผ ์์ฒด์ ์ผ๋ก ๊ตฌ์ถํ ์ ์์์ต๋๋ค. ๋ฌผ๋ก , ์ฌ์ ํ ์ฑ์ฅํ ์ฌ์ง๊ฐ ์์ต๋๋ค. ๋ฏธ๋์๋ ์ฌ๋ฌ ๊ท์น์ ์ฒ๋ฆฌํ๊ณ , ์ฌ๋ฌ ์ค๋ ๋์์ ์์ ํ๊ณ , CRD์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ชจ๋ํฐ๋งํ ์ ์์ ๊ฒ์ ๋๋ค.
์ฝ๋๋ฅผ ์์ธํ ์ดํด๋ณด๊ธฐ ์ํด ์ฝ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ฃ์์ต๋๋ค.
์ถ์ : Kubernetes ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ์๋ ๋๋ฌด ๊ฒ์ผ๋ฅด๊ฑฐ๋ Bash ์ฌ์ฉ์ ๋ ์ต์ํด์ง ๊ฒฝ์ฐ, ์ฐ๋ฆฌ ๋๋ฃ๋ค์ด ๋ค์ ํ์์ผ๋ก ๊ธฐ์ฑ ์๋ฃจ์
์ ์ค๋นํ์ต๋๋ค.
PPS
๋ธ๋ก๊ทธ์์๋ ์ฝ์ด๋ณด์ธ์.
- ยซ
Kubernetes ํด๋ฌ์คํฐ๋ฅผ ์ค๋นํ๋ ๊ฒ์ด ์ฝ๊ณ ํธ๋ฆฌํฉ๋๊น? ์ ๋์จ ์ด์์ ๋ฐํ "; - ยซ
Shell-Operator ์๊ฐ: Kubernetes์ฉ ์ฐ์ฐ์ ์์ฑ์ด ๋์ฑ ์ฌ์์ก์ต๋๋ค. "; - ยซ
Kubernetes ํ์ฅ ๋ฐ ๋ณด์(๊ฐ์ ๋ฐ ๋์์ ๋ณด๊ณ ์) "; - ยซ
Golang์์ Kubernetes์ฉ ์ฐ์ฐ์ ์์ฑ "; - ยซ
Kubernetes์ฉ ์ฐ์ฐ์: ์ํ ์ ์ฅ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ ๋ฐฉ๋ฒ ".
์ถ์ฒ : habr.com