์ด๋ฒ ๊ธ์์๋ ์ฌ์ฉ์ ์ ์ ๋ฆฌ์์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ดํ๋ฆฌ์ผ์ด์ ๋ฐ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๊ด๋ฆฌํ๋ Operator ํจํด์ ๋ํด์ ์์๋ณด๊ณ , ๊ณต์ ๋ฌธ์์ ๋ฐ๋ผ ๊ฐ๋จํ Custom Operator๋ฅผ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
Operator๋?
Operator๊ฐ ๋ฌด์์ธ์ง ์์๋ณด๊ธฐ ์ด์ ์ Controller๋ผ๋ ๊ฐ๋ ์ ๋ํด ๋จผ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
Controller
Kubernetes Patterns ์ฑ ์ ์ฐธ๊ณ ํ๋ฉด Kubernetes์์ ๋ฆฌ์์ค์ Status๋ฅผ ๊ฐ์ํ๋ฉฐ ์ํ๋ ์ํ(Spec)๋ก ๋ง๋๋ ๊ฒ์ Controller๋ผ๊ณ ํฉ๋๋ค.
ํ์ง๋ง, ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋ Controller์ ๊ธฐ๋ฅ ์ด์ธ์๋ ๋ ๋ณต์กํ ์ดํ๋ฆฌ์ผ์ด์ ๊ด๋ฆฌ ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๋ถ๋ช ์กด์ฌํฉ๋๋ค. ์ด๋ฐ ์ํฉ์ ์ปค๋ฒํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด Operator์ ๋๋ค.
Operator
Kubernetes Patterns ์ฑ ์์ ์ ํํ ์ ์๋ฅผ ์ดํด๋ณด๋ฉด Operator๋ CRD(Custom Resource Definitions)์ ์ํธ ์์ฉํ๋ ์ ๊ตํ Reconcil ๊ณผ์ ์ ๋ํ๋ด๋ฉฐ, ์ด๋ฅผ ํตํด ์ด์์๋ ๋ณต์กํ ์ดํ๋ฆฌ์ผ์ด์ ๋๋ฉ์ธ ๋ก์ง์ ์บก์ํํ์ฌ ๊ด๋ฆฌํ ์ ์๊ฒ ๋ฉ๋๋ค.
Develop Custom Operator
๊ทธ๋ ๋ค๋ฉด Operator๋ฅผ ์ด๋ป๊ฒ ๊ฐ๋ฐํ ๊น์?
Operator๋ฅผ ๊ฐ๋ฐํ๊ธฐ ์ํ ๋๊ตฌ๋ก๋ KUDO, kubebuilder, Operator Framework๊ฐ ์กด์ฌํฉ๋๋ค. Operator ๊ฐ๋ฐ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด Custom Resource๋ Controller๋ฅผ ๊ฐ๋ฐํ๋๋ฐ ํ์ํ Boiler Plate๋ฅผ ์ ๊ณตํด์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฐ์๋ ํต์ฌ ๋ก์ง ๊ฐ๋ฐ์ ์ง์คํ ์ ์์ต๋๋ค.
์ต๊ทผ Operator Framework๊ฐ ๋ง์ด ์ฌ์ฉ๋๊ณ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฒ ๊ธ์์๋ Operator Framework๋ฅผ ์ฌ์ฉํด Operator๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
Operator Framework๋ Operator ๊ฐ๋ฐ ํ๋ก์ฐ๋ฅผ ์ ๊ณตํ๋ Operator SDK์ Operator์ ๋ผ์ดํ ์ฌ์ดํด์ ๊ด๋ฆฌํ๋ Operator OLM์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
Operator SDK
Operator SDK ๋ฌธ์๋ฅผ ์ดํด๋ณด๋ฉด Golang, Ansible, Helm์ ์ฌ์ฉํ์ฌ Operator ๊ฐ๋ฐ ํ๋ก์ฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฒ ๊ธ์์๋ Golang์ ์ฌ์ฉํ์ฌ ์งํํ๊ฒ ์ต๋๋ค.
Golang Operator๋ฅผ ํตํด ๊ฐ๋ฐํ Controller๋ ๋ค์๊ณผ ๊ฐ์ ์ฃผ์ ํจํค์ง๋ฅผ ๊ฐ์ง๊ฒ ๋ฉ๋๋ค.
Runtime Controller Package
Controller๊ฐ ๊ด๋ฆฌํ CR์ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ณ SDK Controller Package์ Reconcile Loop๊ฐ ๋์ํ๋๋ก ํฉ๋๋ค.Runtime Manager Package
Kubernetes Client ๋ฐ Cache๋ฅผ ์ด๊ธฐํํฉ๋๋ค.SDK Controller Package
์ค์ Controller ๋ก์ง์ ์ํํ๋ Reconcile Loop์ Runtime Manager Package๋ก ๋ถํฐ ์ ๋ฌ๋ฐ์ Kubernetes Client๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
์ฐธ๊ณ ๋ฌธ์ : https://ssup2.github.io/programming/Kubernetes_Operator_SDK_Golang/
Operator SDK Tutorial
Operator SDK Tutorial์ ์๋ Memcached Operator๋ฅผ ๋ง๋ค์ด๋ณด์. ๋จผ์ working directory๋ฅผ ์์ฑํ ํ, ์ด๊ธฐํ ์์ ์ ์งํํฉ๋๋ค.
โถ operator-sdk init --repo=github.com/seongpyoHong/memcached-operator
โถ ls
Dockerfile Makefile PROJECT bin config go.mod go.sum hack main.go
operator๋ฅผ ์ํ main program์ main.go
๋ก Manager์ ์ด๊ธฐํ ๋ฐ ์คํ์ ๋ด๋นํฉ๋๋ค.
[main.go]
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
LeaderElection: enableLeaderElection,
LeaderElectionID: "9dd21db1.my.domain",
//Namespace: namespace
})
๋ง์ฝ Controller๊ฐ ๊ด์ฐฐํ ๋ฆฌ์์ค์ Namespace๋ฅผ ์ ํํ๊ธฐ ์ํด Options์ Namespace ๊ด๋ จ ์ค์ ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๋ค์์ผ๋ก Memcached Operator๋ฅผ ํตํด ๊ด๋ฆฌํ Memcached CRD์ Controller๋ฅผ ์์ฑํฉ๋๋ค.
โถ operator-sdk create api --group=cache --version=v1alpha1 --kind=Memcached
Create Resource [y/n]
y
Create Controller [y/n]
y
...
...
Memcached CRD
์์ ๋ช
๋ น์ด๋ฅผ ํตํด ์์ฑํ CRD๊ฐ ๊ฐ์ ธ์ผ ํ๋ ์ ๋ณด๋ api/v1alpha1
์๋์ memcached-types.go
์ ์ถ๊ฐํ ์ ์์ต๋๋ค. Spec์ Size๋ ๋์ํด์ผํ๋ Memcached Pod์ ๊ฐ์๋ฅผ ๋ํ๋ด๊ณ , Status์ Nodes๋ ๋์ํ๋ Memcached Pod์ ์ด๋ฆ์ ๋ํ๋
๋๋ค.
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
Size int32 `json:"size"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
Nodes []string `json:"nodes"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// Memcached is the Schema for the memcacheds API
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// MemcachedList contains a list of Memcached
type MemcachedList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Memcached `json:"items"`
}
func init() {
SchemeBuilder.Register(&Memcached{}, &MemcachedList{})
}
CRD ์ ๋ณด๋ฅผ ์ถ๊ฐํ ํ ์
๋ฐ์ดํธ๋ฅผ ์ํด ๋ค์ ์ปค๋งจ๋๋ฅผ ์คํํ๊ณ , Manifest๋ฅผ ์์ฑํฉ๋๋ค. ์๋ ์ปค๋งจ๋๋ฅผ ์คํํ๊ฒ ๋๋ฉด config/crd
์๋์ CRD๊ฐ ์์ฑ๋ฉ๋๋ค.
> make generate
> make manifest
Memcached Controller
controllers/memcached-controller.go
์ controller boilerplate๊ฐ ์์ฑ๋ฉ๋๋ค. Memcached CR์ ์ํด Controller๊ฐ ์คํํด์ผํ ํต์ฌ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Memcached Deployment ์์ฑ
- Deployment Replica๊ฐ Spec์ Size์ ๋์ผํ๋๋ก ๋ณด์ฅ
- Memcached CR์ status ์ ๋ฐ์ดํธ
์ด์ controller.go
์ ์กด์ฌํ๋ ํจ์๋ฅผ ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๋จผ์ SetupWithManager
ํจ์๋ Controller๊ฐ ๊ด๋ฆฌํ๋ CR ๋ฐ ๊ธฐํ ๋ฆฌ์์ค๋ฅผ ๊ฐ์ํ๊ธฐ ์ํด ์ด๋ป๊ฒ ์ปจํธ๋กค๋ฌ๊ฐ ๊ตฌ์ถ๋๋์ง๋ฅผ ์ง์ ํฉ๋๋ค.
func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cachev1alpha1.Memcached{}).
Owns(&appsv1.Deployment{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: 2,
}).Complete(r)
}
์์ ์ฝ๋์์ ๋ํ ์์ธํ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
NewControllerManagedBy()
๋ ๋ค์ํ ์ปจํธ๋กค๋ฌ ์ค์ ์ ํ ์ ์๋ controller builder๋ฅผ ์ ๊ณตํฉ๋๋ค.For(&cachev1alpha1.Memcached{})
์ ์ปจํธ๋กค๋ฌ๊ฐ ๊ด์ฐฐํ๊ธฐ ์ํ ์ฐ์ ์ ์ธ ๋ฆฌ์์ค๋ฅผ Memcached ํ์ ์ผ๋ก ํน์ ํฉ๋๋ค. Memcached ํ์ ์ Add/Update/Delete ์ด๋ฒคํธ์ ๋ํด Reconcil ๋ฃจํ๊ฐ ํด๋น Memcached ๊ฐ์ฒด์ ๋ํ Reconcil ์์ฒญ์ ์ ์กํฉ๋๋ค.Owns(&appsv1.Deployment{})
์ปจํธ๋กค๋ฌ๊ฐ ๊ด์ฐฐํ๊ธฐ ์ํ ์ถ๊ฐ ๋ฆฌ์์ค๋ก Deployments๋ฅผ ์ง์ ํฉ๋๋ค. ์ ์ฝ๋๋ Memcached Object๊ฐ ์์ฑํ Deployment๋ฅผ ๊ด์ฐฐํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋คWithOptions
๋ฅผ ํตํด Controller์ ์ ์ฉํ ๋ค์ํ ์ต์ ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์์ ์ฝ๋๋ Reconcil์ ๋์์ ์ต๋ 2๊ฐ ์ํํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
๋ค์์ผ๋ก Reconcile(req ctrl.Request) ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ํจ์๋ Request๋ฅผ ๋ฐ์์ ๋, ์ด๋ค ๋์์ ์ํํ ์ง ์ ์ํ๋ ๋ถ๋ถ์ ๋๋ค. ์์ธํ ์ฝ๋๋ https://github.com/operator-framework/operator-sdk/blob/master/example/memcached-operator/memcached_controller.go.tmpl ์ ์กด์ฌํ๋ฉฐ, ๊ฐ๋จํ๊ฒ ์ค๋ช ํ๋ฉด ์์์ ์ค๋ช ํ Controller๊ฐ ์คํํ ํต์ฌ๋ก์ง์ ์ํํ๊ฒ ๋ฉ๋๋ค.
Build
์ด์ Operator๋ฅผ ๋น๋ํฉ๋๋ค. ๋น๋ ์ดํ crd๋ฅผ ํ์ธํด๋ณด๋ฉด ์ ์์ ์ผ๋ก ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
> make install
go: creating new go.mod: module tmp
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.3.0
/Users/seongpyo/workspace/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
go: creating new go.mod: module tmp
/Users/seongpyo/workspace/go/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain created
> kubectl get crd
NAME CREATED AT
managedcertificates.networking.gke.io 2020-10-02T05:32:57Z
Run
Operator๋ฅผ ์คํํ๋ ๋ฐฉ๋ฒ์ 2๊ฐ์ง๊ฐ ์กด์ฌํฉ๋๋ค.
- Local์์ Go Program์ผ๋ก ์คํ
- Cluster์ Deployment๋ก ๋ฐฐํฌ
์ด๋ฒ์๋ Local์์ ์คํ์ํค๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ์งํํด๋ณด๊ฒ ์ต๋๋ค.
โถ make run ENABLE_WEBHOOKS=false
go: creating new go.mod: module tmp
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.3.0
/Users/seongpyo/workspace/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
/Users/seongpyo/workspace/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
go run ./main.go
2020-10-02T15:07:51.303+0900 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"}
2020-10-02T15:07:51.304+0900 INFO setup starting manager
2020-10-02T15:07:51.304+0900 INFO controller-runtime.manager starting metrics server {"path": "/metrics"}
2020-10-02T15:07:51.304+0900 INFO controller Starting EventSource {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "source": "kind source: /, Kind="}
2020-10-02T15:07:51.406+0900 INFO controller Starting EventSource {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "source": "kind source: /, Kind="}
2020-10-02T15:07:51.508+0900 INFO controller Starting Controller {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached"}
2020-10-02T15:07:51.508+0900 INFO controller Starting workers {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "worker count": 1}
Operator๊ฐ ์ ์์ ์ผ๋ก ์คํ๋์์ผ๋ฉด ๋ค์์ผ๋ก Operator๊ฐ ๊ด์ฐฐํ CR์ ์์ฑํฉ๋๋ค.
โถ kubectl apply -f cache_v1alpha1_memcached.yaml
memcached.cache.my.domain/memcached-sample created
CR์ ์์ฑํ๋ฉด Operator๋ฅผ ์คํํ๋ ์์์ ์ด๋ฅผ ๊ฐ์งํ๋ ๋ก๊ทธ๊ฐ ๋ฐ์ํฉ๋๋ค.
2020-10-02T15:09:07.588+0900 INFO controllers.Memcached Creating a new Deployment {"memcached": "default/memcached-sample", "Deployment.Namespace": "default", "Deployment.Name": "memcached-sample"}
2020-10-02T15:09:08.243+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:08.256+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:08.256+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:14.221+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:14.254+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:14.263+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:14.263+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
2020-10-02T15:09:15.818+0900 DEBUG controller Successfully Reconciled {"reconcilerGroup": "cache.my.domain", "reconcilerKind": "Memcached", "controller": "memcached", "name": "memcached-sample", "namespace": "default"}
๋ค์ ๊ธ์์๋ Operator SDK๋ฅผ ์ด์ฉํด์ Elasticsearch Operator๋ฅผ ๊ฐ๋ฐํด๋ณด๋ ๊ฒ์ ์งํํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๋ฌธ์ ๊ฐ ์๊ฑฐ๋ ์๋ชป๋ ๋ด์ฉ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ํผ๋๋ฐฑ ๋ถํ๋๋ฆฝ๋๋ค!
์ฐธ๊ณ ์๋ฃ
- https://m.blog.naver.com/alice_k106/221586279079
- https://ssup2.github.io/programming/Kubernetes_Operator_SDK_Golang/
- https://www.redhat.com/en/engage/kubernetes-containers-architecture-s-201910240918?dynamic404=en
- https://olm.operatorframework.io/
- https://sdk.operatorframework.io/
'๐ Kubernetes' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Dynamic Admission Controller ์ฌ์ฉํ๊ธฐ (0) | 2021.04.29 |
---|---|
[Kubernetes ๋ด๋ถ ๊ตฌ์กฐ ์ดํดํ๊ธฐ] 1. ์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ ๊ตฌ์ฑ ์์ (0) | 2021.04.12 |
Pinpoint Agent Helm Chart ์์ฑํ๊ธฐ (0) | 2021.03.06 |
Elasticsearch Operator ๊ฐ๋ฐํ๊ธฐ (0) | 2020.12.18 |
Argo Project๋ฅผ ์ฌ์ฉํ์ฌ CI/CD ๊ตฌ์ถํ๊ธฐ (0) | 2020.12.18 |
๋๊ธ