MGMT ํด๋ฌ์คํฐ์์ OpenstackConfig CR์ ๊ฐ์ํ๊ณ OpenStack ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ์์งํ ๋ค MGMT ํด๋ฌ์คํฐ์ ๋ฐฐํฌ๋ Viola API๋ก ๋ ธ๋๋ณ ์ธํฐํ์ด์ค ์ ๋ณด๋ฅผ ์ ์กํ๋ ์คํผ๋ ์ดํฐ์ ๋๋ค.
- ์ ๋ ฅ: OpenstackConfig CR (openstackProviderID, k8sProviderID, projectID, VM ID ๋ชฉ๋ก + settings/secrets)
- ์ฒ๋ฆฌ: Contrabass โ Keystone โ Neutron ํฌํธ ์กฐํ
- ์ถ๋ ฅ: Viola API๋ก JSON POST (MultiNicNodeConfig ์์ฑ์ฉ, subnetIDs ์ฐ์ /์์ผ๋ฉด subnetID/subnetName)
- ์ ์ฅ: ์คํผ๋ ์ดํฐ ๋ด๋ถ Inventory API + ํ์ผ ๊ธฐ๋ฐ DB(JSON)์ ์ต์ ์ํ upsert (UI ์กฐํ์ฉ)
- ์๋ธ๋ท ์ง์ :
subnetIDs>subnetID>subnetName์ฐ์ ์์ ์ ์ฉ- ์ฌ๋ฌ ์๋ธ๋ท ์ง์ ๊ฐ๋ฅ (์:
subnetIDs: [subnet-a, subnet-b])
- ์ฌ๋ฌ ์๋ธ๋ท ์ง์ ๊ฐ๋ฅ (์:
- ์ธํฐํ์ด์ค ์ํ: ๋
ธ๋๋น ์ต๋ 10๊ฐ (
multinic0~multinic9) - ๊ธฐ์ค ์์ : OpenstackConfig ์์ฑ ์๊ฐ ์ดํ์ ์์ฑ๋ ํฌํธ๋ง ์ฒ๋ฆฌ
- ํฌํธ ํํฐ:
settings.openstackPortAllowedStatuses์ ํฌํจ๋ ํฌํธ๋ง ์ฒ๋ฆฌ - Viola POST ํ์๊ฐ: k8sProviderID๊ฐ ์์ด์ผ
x-provider-idํค๋๋ก ์ ์ก ๊ฐ๋ฅ - Agent ์ง์ OS: Ubuntu(netplan), RHEL(NetworkManager) ๊ธฐ๋ฐ ์์ ์ค์
- ์์ธ ๋ด์ฉ์
../multinic-agent/README.md์ฐธ๊ณ
- ์์ธ ๋ด์ฉ์
์ฃผ์:
subnetIDs>subnetID>subnetName์์๋ก ์ ์ฉํฉ๋๋ค.subnetName์ ๋คํธ์ํฌ๋ช ์ด ์๋๋ผ ์๋ธ๋ท ์ด๋ฆ์ ๋๋ค. (๋์ผ ์ด๋ฆ์ด ์์ผ๋ฉด ์ค๋ฅ)vmNames์๋ VM ID(UUID) ๋ฅผ ๋ฃ์ด์ผ ํฉ๋๋ค.- nodeName์ Nova ์๋ฒ ์ด๋ฆ์ ์ฌ์ฉํ๋ฉฐ, ํ์ ์
settings.openstackNodeNameMetadataKey๋ก metadata ๊ฐ์ ์ฐ์ ์ฌ์ฉํ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค. - ํฌํธ ์ํ๊ฐ
settings.openstackPortAllowedStatuses์ ํฌํจ๋์ง ์๊ฑฐ๋, ๋์ ๋ ธ๋์ ์ธํฐํ์ด์ค๊ฐ ๋น์ด ์์ผ๋ฉด ํด๋น ๋ ธ๋๋ ์ ์ก์์ ์ ์ธ๋ฉ๋๋ค. - OpenstackConfig ์์ฑ ์๊ฐ ์ดํ์ ์์ฑ๋ ํฌํธ๋ง ์ฒ๋ฆฌํฉ๋๋ค.
- DOWN ํฌํธ๊ฐ ๋จ์ ์์ผ๋ฉด ๋น ๋ฅธ ์ฌ์๋ ํ(๊ธฐ๋ณธ 5ํ) ๋๋ฆฐ ์ฃผ๊ธฐ๋ก ์ฌ์ ์กํฉ๋๋ค.
- ์ธํฐํ์ด์ค๋
subnetIDs์์๋๋ก ์ ๋ ฌํ ๋ค normalize ๋จ๊ณ์์ MAC/PortID ๊ธฐ์ค์ผ๋ก ์ฌ์ ๋ ฌ๋ฉ๋๋ค. (ํ์ฌ ๋์: ์ต์ขmultinic์์๋ MAC ๊ธฐ์ค)
- Go 1.25+
- Kubernetes ํด๋ฌ์คํฐ ์ ๊ทผ ๊ถํ
- Contrabass/OpenStack API ์ ๊ทผ ๊ฐ๋ฅ
์ด์ ํ๊ฒฝ์์๋ Helm ๋ฐฐํฌ ์ ์ด๋ฏธ์ง ์ ๋ณด๋ง ์ค์ ํ๊ณ , ์ค์ ์ ์ ์ ๋ณด๋ OpenstackConfig CR๋ก ์ ๋ฌํฉ๋๋ค.
ํ์ ํ๋:
subnetIDs๋๋subnetID๋๋subnetName(subnetIDs/subnetID ๊ถ์ฅ)vmNames: VM ID(UUID) ๋ชฉ๋กcredentials.openstackProviderIDcredentials.k8sProviderIDcredentials.projectID- Contrabass ์ํธํ ํค
secrets.contrabassEncryptKeySecretRef๋๋settings.contrabassEncryptKey
๋์ ๊ท์น:
subnetIDs๊ฐ ์์ผ๋ฉดsubnetID/subnetName์ ๋ฌด์๋ฉ๋๋ค.
์ ํ ํ๋:
settings: Contrabass/Viola/OpenStack/ํด๋ง ์ต์ secrets.contrabassEncryptKeySecretRef(๊ถ์ฅ)
๊ธฐ๋ณธ ์ํธํ ํค:
<namespace>/contrabass-encrypt-keySecret์CONTRABASS_ENCRYPT_KEY๋ฅผ ์๋ ์ฌ์ฉ- Secret์ด ์์ผ๋ฉด
settings.contrabassEncryptKey๋ฅผ ์ฌ์ฉ
Secret ์์:
apiVersion: v1
kind: Secret
metadata:
name: contrabass-encrypt-key
namespace: multinic-system
type: Opaque
stringData:
CONTRABASS_ENCRYPT_KEY: "conbaEncrypt2025"Viola API ์ฃผ์:
spec.settings.violaEndpoint๊ฐ ์์ผ๋ฉด CR๋ณ๋ก ์ฌ์ฉ- ์์ผ๋ฉด Helm values์
operatorConfig.violaEndpoint(=VIOLA_ENDPOINT)๋ฅผ ์ฌ์ฉ
OpenstackConfig ์์:
apiVersion: multinic.example.com/v1alpha1
kind: OpenstackConfig
metadata:
name: openstackconfig-sample
namespace: multinic-system
spec:
subnetIDs:
- "8f0d5f5b-8f3f-4b2b-9c4c-8c9f7c36d1f2" # OpenStack subnet ID
- "dae4f6ea-76ae-4e56-b3a5-87e6df94a574" # OpenStack subnet ID
vmNames:
- "08186d75-754e-449c-b210-c0ea822727a7" # OpenStack VM ID(UUID)
- "c863944f-5cfe-4e05-805f-7522f3e9b080" # OpenStack VM ID(UUID)
- "fbfd0e4d-a4bb-4769-bceb-46cb4b0dc3c5" # OpenStack VM ID(UUID)
credentials:
openstackProviderID: "66da2e07-a09d-4797-b9c6-75a2ff91381e" # Contrabass Provider ID
projectID: "0d5f63c52fc94aeeb767e69790fa73c8" # OpenStack Project(Tenant) ID
k8sProviderID: "f5861c22-b252-42b5-a0c5-cfb1d245c819" # K8s Provider ID (Viola ๋ผ์ฐํ
ํค)
settings:
contrabassEndpoint: "https://expert.bf.okestro.cloud" # Contrabass API base URL
violaEndpoint: "http://viola-api.multinic-system.svc.cluster.local:8080" # Viola API base URL
openstackPortAllowedStatuses:
- "ACTIVE" # ๋์ ํฌํธ ์ํ
- "DOWN" # ๋์ ํฌํธ ์ํ
pollFastInterval: "10s" # ๋ณ๊ฒฝ ์งํ ๋น ๋ฅธ ํด๋ง ์ฃผ๊ธฐ
pollSlowInterval: "2m" # ์์ ๊ตฌ๊ฐ ํด๋ง ์ฃผ๊ธฐ
secrets:
contrabassEncryptKeySecretRef:
name: contrabass-encrypt-key # adminPw ๋ณตํธํ ํค Secret ์ด๋ฆ
key: CONTRABASS_ENCRYPT_KEY # Secret data key์์ธํ ํ๋ฆ๋/์ํคํ
์ฒ/๋จ๊ณ ์ค๋ช
์ docs/FLOW.md ์ฐธ๊ณ .
flowchart LR
subgraph MGMT["MGMT Cluster"]
OP[Multinic Operator]
VA[Viola API]
INV[Inventory API]
CAPI[Contrabass API]
end
subgraph OS["OpenStack"]
KS[Keystone]
NE[Neutron]
NO[Nova]
end
subgraph BIZ["Biz Cluster"]
KAPI[K8s API Server]
CR[MultiNicNodeConfig CR]
end
OP -->|Provider ์กฐํ| CAPI
OP -->|Token ์์ฒญ| KS
OP -->|Port ์กฐํ| NE
OP -->|NodeName ์กฐํ| NO
OP -->|๋
ธ๋๋ณ ์ธํฐํ์ด์ค POST| VA
VA -->|CR ์ ์ฉ ์์ฒญ| KAPI
KAPI -->|CR ์์ฑ/๊ฐฑ์ | CR
OP -->|์ํ ์ ์ฅ| INV
sequenceDiagram
autonumber
participant CR as OpenstackConfig
participant OP as Operator
participant CB as Contrabass (MGMT)
participant KS as Keystone
participant NE as Neutron
participant NO as Nova
participant VA as Viola API (MGMT)
participant K8S as Biz K8s API
CR->>OP: CR ์์ฑ/์์
OP->>CB: Provider ์กฐํ
CB-->>OP: Keystone URL + Admin ๊ณ์
OP->>KS: ํ ํฐ ์์ฒญ
KS-->>OP: ํ ํฐ + ์นดํ๋ก๊ทธ
OP->>NE: Port ์กฐํ (device_id=VM ID)
OP->>NO: ์๋ฒ ์ ๋ณด ์กฐํ (nodeName)
OP->>VA: ๋
ธ๋๋ณ ์ธํฐํ์ด์ค POST
VA->>K8S: CR ์ ์ฉ ์์ฒญ
K8S-->>VA: ์ ์ฉ ๊ฒฐ๊ณผ
- OpenstackConfig CR ์ด๋ฒคํธ ๋ฐ์
- Contrabass provider ์กฐํ ๋ฐ adminPw ๋ณตํธํ
- Keystone ํ ํฐ ๋ฐ๊ธ (์๋น์ค ์นดํ๋ก๊ทธ ํฌํจ)
- Neutron ์๋ํฌ์ธํธ ๊ฒฐ์ (์นดํ๋ก๊ทธ ๋๋ settings)
- subnetIDs/subnetID/subnetName โ subnet/network ์กฐํ (CIDR/MTU ํ๋ณด)
- Neutron ํฌํธ ์กฐํ (device_id == VM ID)
- Nova ์๋ฒ ์กฐํ๋ก nodeName ๊ฒฐ์ (metadata key > server name > vmID)
- ๋์ subnet์ ํฌํจ๋ ํฌํธ๋ง ์ ๋ณ
- ๋ ธ๋๋ณ ์ธํฐํ์ด์ค ๊ตฌ์ฑ
- Viola API POST
- ํ์ผ ๊ธฐ๋ฐ DB(JSON) ์ต์ ์ํ upsert (
k8sProviderID+ nodeName ๊ธฐ์ค) - ๋ณ๊ฒฝ ์งํ ๋น ๋ฅธ ํด๋ง โ ์์ ๊ตฌ๊ฐ์ ๋๋ฆฐ ํด๋ง
- ์์ ์กฐ๊ฑด: OpenstackConfig CR ์์ฑ/์์ ๋๋ VM ํฌํธ ๋ถ์ฐฉ
- CR ์์ฑ ์๊ฐ ์ดํ์ ์์ฑ๋ ํฌํธ๋ง ์ฒ๋ฆฌ (๊ธฐ์กด ํฌํธ ์ ์ธ)
- OpenstackConfig ์
๋ ฅ ์๋ฏธ:
subnetIDs/subnetID/subnetName: ๋ฉํฐ NIC ๋์ ์๋ธ๋ท ์ง์ vmNames: ํฌํธ ์กฐํ ๋์ VM ID(device_id ๋งค์นญ)credentials.openstackProviderID: Contrabass ์กฐํ์ฉ Provider IDcredentials.projectID: Keystone ํ ํฐ ๋ฐ๊ธ ๋์ Project IDcredentials.k8sProviderID: Viola ๋ผ์ฐํ ํค(x-provider-id)settings.violaEndpoint: Viola API POST ์ฃผ์contrabassEncryptKey: adminPw ๋ณตํธํ ํค(Secret ๋๋ settings)
- Token/Service Catalog:
- Keystone ํ ํฐ์ Neutron/Nova ํธ์ถ ์ธ์ฆ์ ํ์
- Service Catalog๋ ์๋น์ค๋ณ ์๋ํฌ์ธํธ URL ๊ฒฐ์ ์ ์ฌ์ฉ
- Port/NodeName ์กฐํ:
- Neutron์์ VM ID ๊ธฐ๋ฐ ํฌํธ๋ฅผ ์กฐํ ํ ์๋ธ๋ท/์ํ ํํฐ ์ ์ฉ
- Nova ์กฐํ๋ก K8s nodeName ๊ฒฐ์ (metadata key ์ฐ์ , ์์ผ๋ฉด ์๋ฒ ์ด๋ฆ)
Operator๊ฐ OpenStack ํฌํธ ์ ๋ณด๋ฅผ ์์งํ ๋ค MGMT ํด๋ฌ์คํฐ์ ๋ฐฐํฌ๋ Viola API๋ก POST ์์ฒญ์ ๋ณด๋
๋๋ค.
Viola API ์ฃผ์๋ spec.settings.violaEndpoint๊ฐ ์ฐ์ ์ด๋ฉฐ, ์์ผ๋ฉด Helm values์
operatorConfig.violaEndpoint๋ก ์ค์ ํฉ๋๋ค.
- Endpoint:
POST /v1/k8s/multinic/node-configs - Headers:
x-provider-id(string, required):OpenstackConfig.spec.credentials.k8sProviderID
- Request Body: ๋ ธ๋๋ณ MultiNicNodeConfig ๋ชฉ๋ก(JSON ๋ฐฐ์ด)
์์ฒญ ํ๋:
| ๊ตฌ๋ถ | key | type | required | description |
|---|---|---|---|---|
| Body | nodeName |
string | O | K8s ๋ ธ๋๋ช |
| Body | instanceId |
string | O | OpenStack VM ID |
| Body | interfaces |
array | O | ๋ ธ๋์ ๋ถ์ฐฉ๋ ์ธํฐํ์ด์ค ๋ชฉ๋ก |
| Body | interfaces[].id |
int | O | 0~9 |
| Body | interfaces[].name |
string | O | multinic0~multinic9 |
| Body | interfaces[].macAddress |
string | O | MAC ์ฃผ์ |
| Body | interfaces[].address |
string | O | IPv4 ์ฃผ์ |
| Body | interfaces[].cidr |
string | O | ์๋ธ๋ท CIDR |
| Body | interfaces[].mtu |
int | O | MTU |
x-provider-id ๊ฐ์ OpenstackConfig.spec.credentials.k8sProviderID๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์์ (์์ฒญ ํค๋ ํฌํจ, 2๊ฐ ๋ ธ๋/๊ฐ 3๊ฐ ์ธํฐํ์ด์ค):
POST /v1/k8s/multinic/node-configs HTTP/1.1
Host: viola-api.example.com
Content-Type: application/json
x-provider-id: f5861c22-b252-42b5-a0c5-cfb1d245c819
[
{
"nodeName": "worker-1",
"instanceId": "i-0123456789abcdef0",
"interfaces": [
{
"id": 0,
"name": "multinic0",
"macAddress": "00:1A:2B:3C:4D:5E",
"address": "192.168.1.100",
"cidr": "192.168.1.0/24",
"mtu": 1500
},
{
"id": 1,
"name": "multinic1",
"macAddress": "00:1A:2B:3C:4D:5F",
"address": "192.168.1.101",
"cidr": "192.168.1.0/24",
"mtu": 1500
},
{
"id": 2,
"name": "multinic2",
"macAddress": "00:1A:2B:3C:4D:60",
"address": "192.168.1.102",
"cidr": "192.168.1.0/24",
"mtu": 1500
}
]
},
{
"nodeName": "worker-2",
"instanceId": "i-0fedcba9876543210",
"interfaces": [
{
"id": 0,
"name": "multinic0",
"macAddress": "00:1A:2B:3C:4D:61",
"address": "192.168.2.10",
"cidr": "192.168.2.0/24",
"mtu": 1500
},
{
"id": 1,
"name": "multinic1",
"macAddress": "00:1A:2B:3C:4D:62",
"address": "192.168.2.11",
"cidr": "192.168.2.0/24",
"mtu": 1500
},
{
"id": 2,
"name": "multinic2",
"macAddress": "00:1A:2B:3C:4D:63",
"address": "192.168.2.12",
"cidr": "192.168.2.0/24",
"mtu": 1500
}
]
}
]์ฐจํธ ๊ฒฝ๋ก: deployments/helm
Helm values์๋ ์ด๋ฏธ์ง ์ ๋ณด๊ฐ ํ์์ ๋๋ค. Viola API ์ฃผ์๋ CR์์ ์ง์ ํ์ง ์๋ ๊ฒฝ์ฐ์๋ง Helm values๋ก ์ค์ ํฉ๋๋ค.
๋ฐฐํฌ ์์:
helm upgrade --install multinic-operator deployments/helm \
-n multinic-operator-system --create-namespace \
--set image.repository=nexus.okestro-k8s.com:50000/multinic-operator \
--set image.tag=dev-20260113062309 \
--set image.pullSecrets[0].name=nexus-regcredvalues.yaml ์์ฑ ์์(ํ์):
image:
repository: nexus.okestro-k8s.com:50000/multinic-operator
tag: "dev-20260113062309"
pullSecrets:
- name: nexus-regcred
operatorConfig:
# CR์์ violaEndpoint๋ฅผ ์ง์ ํ์ง ์์ ๋๋ง ์ฌ์ฉ
violaEndpoint: "https://viola-api.example.com"values.yaml ์์ฑ ์์(์ ํ):
inventory:
enabled: true
service:
port: 18081
persistence:
enabled: falsespec.settings.violaEndpoint๋ฅผ ์ฌ์ฉํ๋ฉด CR๋ณ๋ก Viola API ์ฃผ์๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
CR์ ๊ฐ์ด ์์ผ๋ฉด operatorConfig.violaEndpoint๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
๋ฐฐํฌ ์์ ์์:
deployments/helm/values.yamlํธ์งhelm upgrade --install ... -f values.yaml๋ก ๋ฐฐํฌcontrabass-encrypt-keySecret ์์ฑ- OpenstackConfig CR ์ ์ฉ
์ฌ๋ด๋ง์์ ์ธํฐ๋ท ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ ๋๋ ์ด๋ฏธ์ง tar๋ฅผ ์ฎ๊ฒจ์ ๋ก๋ํ ๋ค ์ฌ๋ด Nexus๋ก pushํ๊ณ Helm values์ ๋ฐ์ํฉ๋๋ค.
์ด๋ฏธ์ง tar ๊ฒฝ๋ก:
images/multinic-operator_dev-20260113062309.tar
์์:
# ์ด๋ฏธ์ง ๋ก๋
nerdctl load -i images/multinic-operator_dev-20260113062309.tar
# Nexus์ ํ๊ทธ/ํธ์
nerdctl tag multinic-operator:dev-20260113062309 nexus.okestro-k8s.com:50000/multinic-operator:dev-20260113062309
nerdctl push nexus.okestro-k8s.com:50000/multinic-operator:dev-20260113062309์คํผ๋ ์ดํฐ๊ฐ ๊ณ์ฐํ ์ต์ ๋
ธ๋๋ณ ์ธํฐํ์ด์ค ์ค๋
์ท์ ์กฐํํ๋ ๋ด๋ถ API์
๋๋ค.
UI ์กฐํ/๋๋ฒ๊น
์ฉ๋๋ก ์ฌ์ฉํ๋ฉฐ, ์ค์ ์ ์ฉ ์ํ๋ Biz ํด๋ฌ์คํฐ์ MultiNicNodeConfig๊ฐ ๊ธฐ์ค์
๋๋ค.
๋ ธ์ถ API (3๊ฐ):
- ํด๋ฌ์คํฐ(Provider) ์์ฝ ์กฐํ:
GET /v1/interfaces/providers - ํน์ ํด๋ฌ์คํฐ ์ ์ฒด ๋
ธ๋ ์กฐํ:
GET /v1/interfaces/node-configs?providerId=...providerId๋ k8sProviderID์ด๋ฉฐ ํ์
- instanceId ๋จ๊ฑด ์กฐํ:
GET /v1/interfaces/node-configs/by-instance/{instanceId}?providerId=...instanceIdํ์,providerId๋ ์ค๋ณต ๋ฐฉ์ง๋ฅผ ์ํด ๊ถ์ฅ
Kubernetes Service:
- Kustomize:
inventory-service(port 18081, namespacesystem) - Helm:
<release>-multinic-operator-inventory(port 18081)
์ฃผ์: ํ์ผ ๊ธฐ๋ฐ ์ ์ฅ์์ด๋ฏ๋ก ์คํผ๋ ์ดํฐ๋ 1๊ฐ replica๋ก ์ด์ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์ง์ ์ ์ฅ์ด ํ์ํ๋ฉด config/manager/manager.yaml์ emptyDir๋ฅผ PVC๋ก ๊ต์ฒดํ์ญ์์ค.
Swagger ๋ฌธ์(Operator -> Viola POST ํ์ด๋ก๋):
GET /openapi.yamlGET /docs(Swagger UI, CDN ์ฌ์ฉ)- POST(viola) + Interfaces API(์กฐํ์ฉ)๊ฐ ํฌํจ๋ฉ๋๋ค.
- Swagger๋ ํ์ฌ ์ ์ํ ์ฃผ์(ํธ์คํธ/ํฌํธ)๋ฅผ ๊ธฐ์ค์ผ๋ก ํธ์ถํฉ๋๋ค.
kubectl -n multinic-operator-system port-forward svc/<inventory-service-name> 18081:18081
curl -s "http://127.0.0.1:18081/v1/interfaces/providers"
curl -s "http://127.0.0.1:18081/v1/interfaces/node-configs?providerId=<k8s-provider-id>"
curl -s "http://127.0.0.1:18081/v1/interfaces/node-configs/by-instance/<instanceId>?providerId=<k8s-provider-id>"์ถ์ฒ ์กฐํ ํ๋ฆ:
/v1/interfaces/providers๋ก providerId ๋ชฉ๋ก ํ์ธ- ํด๋น providerId๋ก
/v1/interfaces/node-configs์กฐํ - ํ์ํ instanceId๋ก
/v1/interfaces/node-configs/by-instance/{instanceId}์กฐํ- ๋์ผ instanceId๊ฐ ๊ฒน์น๋ฉด providerId๋ฅผ ์ถ๊ฐ
/v1/interfaces/providers ์๋ต ์์:
{
"providers": [
{
"providerId": "f5861c22-b252-42b5-a0c5-cfb1d245c819",
"nodeCount": 3,
"updatedAt": "2026-01-12T02:51:32Z",
"nodes": [
{
"providerId": "f5861c22-b252-42b5-a0c5-cfb1d245c819",
"nodeName": "infra01",
"instanceId": "ec4bdcc1-dbcc-4c5d-88a4-581a14beca2d",
"interfaceCount": 3,
"updatedAt": "2026-01-12T02:51:32Z"
}
]
}
]
}๋
ธ๋ ์์ธ ์๋ต ์์ (/v1/interfaces/node-configs, /by-instance):
[
{
"providerId": "f5861c22-b252-42b5-a0c5-cfb1d245c819",
"nodeName": "manager01",
"instanceId": "08186d75-754e-449c-b210-c0ea822727a7",
"config": {
"nodeName": "manager01",
"instanceId": "08186d75-754e-449c-b210-c0ea822727a7",
"interfaces": [
{
"id": 0,
"name": "multinic0",
"macAddress": "fa:16:3e:aa:bb:cc",
"address": "10.0.0.10",
"cidr": "10.0.0.0/24",
"mtu": 1450
}
]
},
"lastConfigHash": "a69f59021cf9a8f7",
"updatedAt": "2026-01-11T01:23:45Z"
}
]์๋ต ์ฝ๋:
200 OK: ์กฐํ ์ฑ๊ณต400 Bad Request: nodeName ๋๋ฝ ๋ฑ ์์ฒญ ์ค๋ฅ404 Not Found: ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ ์์503 Service Unavailable: inventory ์ ์ฅ์ ๋นํ์ฑ
OpenstackConfig์๋ ๋๊ธฐํ ์ํ๋ฅผ ๋ํ๋ด๋ Conditions๊ฐ ๊ฐฑ์ ๋ฉ๋๋ค.
Ready: ๋๊ธฐํ ์ฑ๊ณต ์ฌ๋ถDegraded: ์ค๋ฅ ๋ฐ์ ์ฌ๋ถ
์ถ๊ฐ ์ํ ํ๋:
lastSyncedAt: ๋ง์ง๋ง ์ฑ๊ณต ๋๊ธฐํ ์๊ฐ(Reason=Synced/NoChange์ผ ๋ ๊ฐฑ์ )lastError: ๋ง์ง๋ง ์ค๋ฅ ๋ฉ์์ง
make install
make deploy IMG=<registry>/multinic-operator:tag์ํ CR ์ ์ฉ:
kubectl apply -k config/samples/Viola ๊ฐ๋ฐ API๊ฐ ์ค๋น๋๊ธฐ ์ ๊น์ง ์๋ ํ ์คํธ์ฉ API๋ฅผ ๋ฐฐํฌํด POST ์์ ๋ฐ CR ์์ฑ๊น์ง ํ์ธํ ์ ์์ต๋๋ค.
# ๋ผ์ฐํ
Secret(viola-api-routing)์ ๋จผ์ ์ค๋นํด์ผ ํฉ๋๋ค.
kubectl apply -f config/test/viola-test-api.yaml๋์ ๋ฐฉ์:
- POST payload๋ฅผ
MultiNicNodeConfig๋ก ๋ณํ kubectl apply -f๋ก CR ์์ฑ/๊ฐฑ์
API ๋ฌธ์(ํ ์คํธ์ฉ):
-
GET /healthz- Response:
200 OK+ok(plain text)
- Response:
-
GET /openapi.yaml- OpenAPI ์คํ (YAML)
-
GET /docs- Swagger UI (CDN ์ฌ์ฉ, ์คํ๋ผ์ธ์์๋ ๋์ํ์ง ์์ ์ ์์)
-
POST /v1/k8s/multinic/node-configs- Headers:
Content-Type: application/jsonx-provider-id(required, ๋ผ์ฐํ ํค)
- Request Body:
[]NodeConfig(JSON ๋ฐฐ์ด) - Response
200 OK:{"applied":3,"output":"multinicnodeconfig.multinic.io/node-1 configured"} - Response
400 Bad Request:- ์๋ชป๋ JSON/ํ์ ํ๋ ๋๋ฝ/๋ผ์ฐํ ์ค์ ์ค๋ฅ
- Response
500 Internal Server Error:kubectl apply์คํจ(๊ฒ์ฆ ์คํจ/์ ์ ์คํจ ๋ฑ)
- Headers:
๋ผ์ฐํ (ํ ์คํธ์ฉ):
- ์์ฒญ ํค๋
x-provider-id๋ฅผ ๊ธฐ์ค์ผ๋ก ๋์ ํด๋ฌ์คํฐ๋ฅผ ์ ํํฉ๋๋ค.- ๊ฐ์
OpenstackConfig.spec.credentials.k8sProviderID๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ๊ฐ์
ROUTING_CONFIG์ ๋ผ์ฐํ ํ์ผ์ ์ง์ ํ๋ฉด providerId๋ณ๋ก ๋์ ํด๋ฌ์คํฐ๋ฅผ ์ ํํฉ๋๋ค.- ๋ชจ๋:
ssh(ํ ์คํธ ๊ถ์ฅ): ์๊ฒฉ Bastion์ SSH๋ก ์ ์ํดkubectl apply.local:kubeApiServer/kubeToken/kubeCaPath๋ก ์๊ฒฉ ํด๋ฌ์คํฐ์kubectl apply.
- ์ํ ํ์ผ:
config/test/viola-routing.sample.yaml strict: true๋ก ์ค์ ํ๋ฉด providerId๊ฐ ์๊ฑฐ๋ ๋งค์นญ ์คํจ ์ 400์ผ๋ก ์คํจํฉ๋๋ค.- SSH ๋ชจ๋๋
sshpass๊ฐ ํ์ํ๋ฉฐ ํ ์คํธ์ฉ Viola API ์ด๋ฏธ์ง์๋ ํฌํจ๋์ด ์์ต๋๋ค.
๋ผ์ฐํ Secret ์์ฑ ์์(ํ ์คํธ์ฉ):
# routing.yaml๊ณผ ca.crt๋ฅผ ์ค๋นํ ๋ค Secret ์์ฑ
kubectl -n multinic-system create secret generic viola-api-routing \
--from-file=routing.yaml=./routing.yaml \
--from-file=ca.crt=./ca.crt๋ผ์ฐํ ์์:
strict: true
targets:
- providerId: "f5861c22-b252-42b5-a0c5-cfb1d245c819"
mode: ssh
namespace: "multinic-system"
sshHost: "192.168.3.170"
sshUser: "root"
sshPort: 22
sshPass: "cloud1234"
kubectlPath: "kubectl"์ด๋ฏธ์ง ๋น๋ ์์:
nerdctl build -f Dockerfile.viola-test-api -t <registry>/multinic-viola-test-api:dev .- 3๊ฐ ๋ ธ๋์ 3๊ฐ ํฌํธ์ฉ ์ผ๊ด ์ถ๊ฐ ํ CR ๋ฐ์ ํ์ธ
- ํฌํธ ์ ๊ฑฐ ์ CR ๋ฐ์ ํ์ธ
- DOWN ํฌํธ ํํฐ๋ง(
openstackPortAllowedStatuses=ACTIVE) ๋์ ํ์ธ - ์ค์ ๋๋ฝ ์ ConfigError ๋ฐ์/๋ณต๊ตฌ ํ์ธ (
contrabassEndpoint) - ์ค๋ณต ๋ณ๊ฒฝ ์คํต(
no changes detected) ํ์ธ - ์๋ชป๋ VM ID/Project ID ์ ๋ ฅ ์ ์ค๋ฅ ์ฒ๋ฆฌ ํ์ธ
- Viola API ์ฅ์ /timeout ์ ์ฌ์๋ ๋์ ํ์ธ
- Inventory API ์กฐํ ์๋ต ํ์ธ
- Interfaces API: providerId ๋ฏธ์ ๋ ฅ ์ 400 ํ์ธ
- Interfaces API: ์๋ชป๋ instanceId ์กฐํ ์ 404 ํ์ธ
- Viola API: x-provider-id ๋๋ฝ ์ 400 ํ์ธ
- Viola API: ์ ์ payload POST 200 ํ์ธ
- ๋ฉํฐ ์๋ธ๋ท(test/test2) ๋์ ๋ถ์ฐฉ ๋์ ํ์ธ
- worker/master ๋ค์ค ๋ ธ๋ ๋์ ์ ์ฉ ํ์ธ
- Viola API ๋ผ์ฐํ (ssh) ์ ์ฉ ํ์ธ
- OpenstackConfig ์์ฑ ์๊ฐ ์ดํ ํฌํธ๋ง ์ฒ๋ฆฌ๋๋์ง ํ์ธ
- ๋
ธ๋๋น ์ธํฐํ์ด์ค 10๊ฐ ์ด๊ณผ ์ 10๊ฐ๋ง ์ ์ก(
multinic0~9) ํ์ธ -
subnetIDs์์๋๋ก ์ธํฐํ์ด์ค ๋งคํ๋๋์ง ํ์ธ -
subnetIDs์์ ๋ณ๊ฒฝ ์ ์ต์ข ์ธํฐํ์ด์ค ์์๊ฐ MAC ๊ธฐ์ค์ผ๋ก ์ ์ง๋จ(ํ ๋์) - OpenstackConfig์์
vmNames์ ๊ฑฐ ์ ๊ธฐ์กด Inventory/CR ์ญ์ ๋ ํ์ง ์์(ํ ๋์) - Viola API ์๋ํฌ์ธํธ ์ค๋ฅ ์ Ready/Degraded ๊ฐฑ์ ํ์ธ
- Viola API ์ฅ์ ๋ณต๊ตฌ ํ ์ ์ ๋๊ธฐํ ํ์ธ
- ์ค๋ณต OpenstackConfig ์์ฑ ์ baseline ์ด์ ํฌํธ๋ ์ ์ก๋์ง ์์(ํ ๋์)
- DOWN ํฌํธ๊ฐ ACTIVE๋ก ์ ํ๋๋ฉด ์ ์ ์ ์ก๋จ์ ํ์ธ
- ์คํผ๋ ์ดํฐ ์ฌ์์ ํ Inventory๊ฐ ์ฌ๋๊ธฐํ๋จ์ ํ์ธ