K8s는 서비스를 외부로 노출할 수 있도록 LoadBalancer Service 타입과 Ingress 두 가지 기능을 제공합니다. LoadBalancer Service의 경우, K8s는 로드밸런서 기능 자체는 직접 제공하지 않습니다. 어떤 로드밸런서를 사용하고 어떻게 구성해서 K8s에 제공할지는 모두 사용자의 선택에 달려 있으며, 구성 방식은 매우 다양합니다.
첫 번째는 K8s 클러스터 외부에 로드밸런서를 따로 호스팅하는 방법입니다. 로드밸런서는 K8s 외부에 배치되기 때문에 K8s는 LB의 수명 주기를 직접 관리하지 않습니다. 이 경우 K8s는 CCM 혹은 Load Balancer Spec API (예: 서비스 spec에 LoadBalancerClass 명시) 등의 기능을 사용해서 클러스터 공급자의 인프라에서 제공하는 로드밸런서 기능을 사용하게 됩니다. 이러한 구성 방식은 자연스럽게 외부 네트워크와 내부 클러스터 네트워크를 분리하게 되며, 이를 통해 추가적인 보안 계층을 확보함으로서 Ingress 트래픽을 더 잘 제어할 수 있습니다. 이러한 장점 때문에 퍼블릭 클라우드 서비스는 사실상 이 방법으로 로드밸런서를 배포하며, K8s에서 LoadBalancer 서비스 타입을 external Load Balancer라고 지칭하는 이유도 이 때문입니다.
두 번째 방법은 로드밸런서를 K8s 클러스터 내부에서 직접 실행해서 서비스로 제공하는 방법입니다. 이 방법은 LB 수명 주기 관리가 용이하다는 장점이 있지만, 외부/내부 네트워크의 관리가 조금 더 어렵습니다. On-premise 사용자처럼 모든 서비스와 기능을 K8s 클러스터 안에서 실행하길 바라거나, 소규모 클러스터 단위로 운용하길 원하는 경우 이쪽 방법을 선호할 것입니다. 외부 로드밸런서를 사용할 경우 발생하는 추가 비용 역시 문제가 될 수 있습니다.
이전에 올렸던 블로그 포스트들은 LoxiLB를 클러스터 외부에 배치하여 LoadBalancer 서비스와 연결하는 방법을 기준으로 설명했습니다. 하지만 동시에 관리의 용이성이나, 한정된 리소스 혹은 제한된 배포 환경 때문에 클러스터 내부에서 LoxiLB를 실행하는 방법에 대해서도 많은 요구사항이 있었습니다. 따라서 이번 포스트에서는 클러스터 내에서 LoxiLB 로드밸런서를 실행하는 방법에 대해 설명하겠습니다.
현재 LoxiLB는 양쪽 모드를 원활하게 지원합니다. LoxiLB는 eBPF를 활용하여 구현한 proxy-less 로드밸런서로 기존의 ipvs/iptables 같은 프레임워크를 대체 가능합니다. 기존 로드밸런서들이 이러한 프레임워크 기반에서 구현되었거나 단순히 활용하는데 그친 반면 LoxiLB는 처음부터 구현되었으므로 이러한 기존 로드밸런서들과 비교해 보다 나은 성능을 제공합니다.
이 포스트에서는 K3s + flannel CNI 를 사용해서 4개의 노드로 구성된 클러스터를 생성하고, kube-loxilb를 사용하여 LoxiLB Daemonset을 배포하는 방법을 설명합니다. LoxiLB는 loxilb-lb와 loxilb-peer 두 개의 Daemonset으로 배포됩니다. 실제 LoxiLB 로드밸런서 기능은 loxilb-lb가 담당합니다. loxilb-peer는 LoxiLB의 special peering mode 사용시 Worker 노드에 배포되며, loxilb-lb에 연결 정보를 공유해주는 역할을 합니다.
loxilb-peer는 BGP 기능이 필요할 때 사용 가능한 옵션입니다. BGP 기능을 지원하지 않는 CNI을 사용할 시 유용하게 활용할 수 있습니다. 물론 필요한 경우 다른 BGP 구현과 동시에 사용 역시 가능합니다.
Design considerations for in-cluster LB
LoxiLB는 클러스터 내부에서 로드밸런서 서비스를 제공하기 위해 총 3개의 컴포넌트를 K8s에 배포합니다.
loxilb-lb: Master 노드에서 실행되며 실제 로드밸런싱 기능을 처리합니다. Master 노드는 주로 컨트롤 플레인 애플리케이션이 실행되며, 일반적으로 고가용성을 확보하기 위해 다중 노드로 실행됩니다. loxilb-lb는 모든 마스터 노드에서 실행됨으로서 자연스럽게 로드밸런서 서비스 기능에 고가용성을 제공할 수 있습니다. AWS 같이 Master 노드를 접근할 수 없는 경우, label이나 taint/toleration 등의 기능을 활용해서 사용자가 설정한 임의의 노드에 실행할 수 있습니다.
loxilb-peer: Worker 노드에서 실행됩니다. loxilb-peer는 비침범형(non-intrusive) 컴포넌트로, 단독으로는 아무 기능을 하지 않고 항상 loxilb-lb와 함께 동작합니다. loxilb-lb와 함께 BGP 메쉬를 구성하여 서비스 IP 및 endpoint로 데이터 접근성을 보장합니다.
kube-loxilb: K8s의 LoadBalancer 서비스 타입 spec의 인터페이스와 구현을 제공하는 애플리케이션입니다. LoxiLB in-cluster 모드에서는 모든 loxilb-lb & loxilb-peer 파드 정보를 바탕으로 BGP 메쉬를 동적으로 구성/관리합니다.
LoxiLB는 기존 iptables 및 CNI 구성과 함께 동작합니다. 이때 iptables이나 CNI 설정이 있는 상태에서 ingress 패킷에 접근하기 위해 LoxiLB는 eBPF를 활용합니다. 리눅스 커널이 패킷을 처리하기 전에 eBPF는 ingress 패킷을 hooking해서 LoxiLB에 설정된 LB 룰을 적용합니다.
또한 LoxiLB는 호스트 모드에서 동작함으로서 노드 자체의 인터페이스를 사용할 수 있습니다. 따라서 로드밸런서 구성을 위해 따로 multus 등을 이용해 추가 인터페이스를 사용할 필요가 없습니다.
Bring up the Kubernetes Cluster
이 포스트에서는 테스트 토폴로지를 구축하기 위해 Vagrant를 사용합니다. 다음은 K3s를 사용해서 클러스터를 구성하는 Vagrantfile 예제입니다.
# -*- mode: ruby -*-
# vi: set ft=ruby :
workers = (ENV['WORKERS'] || "2").to_i
#box_name = (ENV['VAGRANT_BOX'] || "ubuntu/focal64")
box_name = (ENV['VAGRANT_BOX'] || "sysnet4admin/Ubuntu-k8s")
box_version = "0.7.1"
Vagrant.configure("2") do |config|
config.vm.box = "#{box_name}"
config.vm.box_version = "#{box_version}"
if Vagrant.has_plugin?("vagrant-vbguest")
config.vbguest.auto_update = false
end
config.vm.define "master1" do |master|
master.vm.hostname = 'master1'
master.vm.network :private_network, ip: "192.168.80.10", :netmask => "255.255.255.0"
master.vm.network :private_network, ip: "192.168.90.10", :netmask => "255.255.255.0"
master.vm.provision :shell, :path => "master1.sh"
master.vm.provider :virtualbox do |vbox|
vbox.customize ["modifyvm", :id, "--memory", 8192]
vbox.customize ["modifyvm", :id, "--cpus", 4]
end
end
config.vm.define "master2" do |master|
master.vm.hostname = 'master2'
master.vm.network :private_network, ip: "192.168.80.11", :netmask => "255.255.255.0"
master.vm.network :private_network, ip: "192.168.90.11", :netmask => "255.255.255.0"
master.vm.provision :shell, :path => "master2.sh"
master.vm.provider :virtualbox do |vbox|
vbox.customize ["modifyvm", :id, "--memory", 8192]
vbox.customize ["modifyvm", :id, "--cpus", 4]
end
end
(1..workers).each do |node_number|
config.vm.define "worker#{node_number}" do |worker|
worker.vm.hostname = "worker#{node_number}"
ip = node_number + 100
worker.vm.network :private_network, ip: "192.168.80.#{ip}", :netmask => "255.255.255.0"
worker.vm.provision :shell, :path => "worker.sh"
worker.vm.provider :virtualbox do |vbox|
vbox.customize ["modifyvm", :id, "--memory", 4096]
vbox.customize ["modifyvm", :id, "--cpus", 2]
end
end
end
end
Vagrantfile 예제를 보면 각 노드의 프로비저닝을 위해서 아래와 같이 스크립트를 사용합니다. master1.sh, master2.sh 그리고 worker.sh 스크립트는 이 링크에서 확인할 수 있습니다.
master.vm.provision :shell, :path => "master1.sh"
이제 다음 명령을 입력하면 클러스터를 자동 생성합니다.
$ vagrant up
...
...
...
worker2: [INFO] systemd: Starting k3s-agent
worker2: Cluster is ready
Deploy kube-loxilb
이 포스트에서는 BGP를 사용하여 외부 클라이언트와 연결할 것입니다. 따라서 kube-loxilb를 배포하기 전에 클라이언트가 접근 가능한 IP와 BGP AS ID를 설정해야 합니다.
다음 명령어로 kube-loxilb.yaml 파일을 다운로드합니다.
$ wget -c https://raw.githubusercontent.com/loxilb-io/loxilb/main/cicd/k3s-flannel-incluster/kube-loxilb.yml
그리고 다음과 같이 kube-loxilb.yaml 파일의 args 항목을 수정합니다.
args:
- --externalCIDR=123.123.123.1/24
- --setBGP=64512
- --setRoles
이제 다음 명령어로 K8s에 kube-loxilb를 배포할 수 있습니다.
vagrant@master1:~$ sudo kubectl apply -f /vagrant/kube-loxilb.yml
serviceaccount/kube-loxilb created
clusterrole.rbac.authorization.k8s.io/kube-loxilb created
clusterrolebinding.rbac.authorization.k8s.io/kube-loxilb created
deployment.apps/kube-loxilb created
vagrant@master1:~$ sudo kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system local-path-provisioner-957fdf8bc-vmndm 1/1 Running 0 10m
kube-system coredns-77ccd57875-2md2m 1/1 Running 0 10m
kube-system metrics-server-648b5df564-44wnc 1/1 Running 0 10m
kube-system loxilb-lb-7v8qm 1/1 Running 0 4m2s
kube-system kube-loxilb-5c5f686ccf-knw2p 1/1 Running 0 28s
Get LoxiLB UP and Running
클러스터의 설정이 끝나면 이제 LoxiLB를 배포할 차례입니다. 다음은 이 포스트에서 사용한 yaml 파일의 예제입니다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: loxilb-lb
namespace: kube-system
spec:
selector:
matchLabels:
app: loxilb-app
template:
metadata:
name: loxilb-lb
labels:
app: loxilb-app
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
tolerations:
- key: "node-role.kubernetes.io/master"
operator: Exists
- key: "node-role.kubernetes.io/control-plane"
operator: Exists
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "node-role.kubernetes.io/master"
operator: Exists
- key: "node-role.kubernetes.io/control-plane"
operator: Exists
containers:
- name: loxilb-app
image: "ghcr.io/loxilb-io/loxilb:latest"
imagePullPolicy: Always
command: [ "/root/loxilb-io/loxilb/loxilb", "--bgp", "--egr-hooks", "--blacklist=cni[0-9a-z]|veth.|flannel." ]
ports:
- containerPort: 11111
- containerPort: 179
securityContext:
privileged: true
capabilities:
add:
- SYS_ADMIN
---
apiVersion: v1
kind: Service
metadata:
name: loxilb-lb-service
namespace: kube-system
spec:
clusterIP: None
selector:
app: loxilb-app
ports:
- name: loxilb-app
port: 11111
targetPort: 11111
protocol: TCP
- name: loxilb-app-bgp
port: 179
targetPort: 179
protocol: TCP
마스터 노드에 접속한 다음 아래 명령어로 LoxiLB를 배포합니다. 배포가 완료되면 loxilb-lb 파드와 loxilb-lb-service 서비스가 생성됩니다.
$ vagrant ssh master1
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-52-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
* Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
just raised the bar for easy, resilient and secure K8s cluster deployment.
https://ubuntu.com/engage/secure-kubernetes-at-the-edge
Last login: Sat Mar 20 18:04:46 2021 from 10.0.2.2
vagrant@master1:~$ sudo kubectl apply -f /vagrant/loxilb.yaml
daemonset.apps/loxilb-lb created
service/loxilb-lb-service created
vagrant@master1:~$ sudo kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-77ccd57875-dwrsm 1/1 Running 0 129m
kube-system kube-loxilb-5c5f686ccf-knw2p 1/1 Running 0 39m
kube-system local-path-provisioner-957fdf8bc-72kcx 1/1 Running 0 129m
kube-system loxilb-lb-9s5qw 1/1 Running 0 19m
kube-system loxilb-lb-sk9cd 1/1 Running 0 19m
kube-system metrics-server-648b5df564-mfg2j 1/1 Running 0 129m
만약 CNI에서 제공하는 BGP 스피커를 사용할 경우 loxilb-peer는 배포하지 않아도 됩니다. 그리고 loxilb.yaml 파일에서 다음과 같이 --bgp 옵션 등을 제거한 후 적용해야 합니다.
containers:
- name: loxilb-app
image: "ghcr.io/loxilb-io/loxilb:latest"
imagePullPolicy: Always
command: [ "/root/loxilb-io/loxilb/loxilb" ]
Deploy loxilb-peer
다음은 loxilb-peer를 배포합니다. 다음은 이 포스트에서 사용한 yaml 파일 예시입니다:
LoxiLB peer mode can be deployed with the yaml file below:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: loxilb-peer
namespace: kube-system
spec:
selector:
matchLabels:
app: loxilb-peer-app
template:
metadata:
name: loxilb-peer
labels:
app: loxilb-peer-app
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "node-role.kubernetes.io/master"
operator: DoesNotExist
- key: "node-role.kubernetes.io/control-plane"
operator: DoesNotExist
containers:
- name: loxilb-peer-app
image: "ghcr.io/loxilb-io/loxilb:latest"
imagePullPolicy: Always
command: [ "/root/loxilb-io/loxilb/loxilb", "--peer" ]
ports:
- containerPort: 11111
- containerPort: 179
securityContext:
privileged: true
capabilities:
add:
- SYS_ADMIN
---
apiVersion: v1
kind: Service
metadata:
name: loxilb-peer-service
namespace: kube-system
spec:
clusterIP: None
selector:
app: loxilb-peer-app
ports:
- name: loxilb-peer-app
port: 11111
targetPort: 11111
protocol: TCP
- name: loxilb-peer-bgp
port: 179
targetPort: 179
protocol: TCP
마스터 노드에서 다음 명령어로 loxilb-peer.yaml 파일을 배포하면 loxilb-peer 파드와 loxilb-peer-service 서비스가 생성됩니다.
vagrant@master1:~$ sudo kubectl apply -f /vagrant/loxilb-peer.yml
daemonset.apps/loxilb-peer created
service/loxilb-peer-service created
vagrant@master1:~$ sudo kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-77ccd57875-dwrsm 1/1 Running 0 154m
kube-system kube-loxilb-5c5f686ccf-knw2p 1/1 Running 0 64m
kube-system local-path-provisioner-957fdf8bc-72kcx 1/1 Running 0 154m
kube-system loxilb-lb-9s5qw 1/1 Running 0 44m
kube-system loxilb-lb-sk9cd 1/1 Running 0 44m
kube-system loxilb-peer-8bh9b 1/1 Running 0 105s
kube-system loxilb-peer-f5fmt 1/1 Running 0 105s
kube-system metrics-server-648b5df564-mfg2j 1/1 Running 0 154m
여기까지 배포가 완료되면, 이제 loxilb-lb 인스턴스 각각에서 자동으로 구성된 BGP 설정을 확인할 수 있습니다:
vagrant@master1:~$ sudo kubectl exec -it loxilb-lb-9s5qw -n kube-system -- bash
root@master1:/# gobgp neigh
Peer AS Up/Down State |#Received Accepted
192.168.80.1 65101 00:34:38 Establ | 1 0
192.168.80.11 64512 00:34:46 Establ | 0 0
192.168.80.101 64512 00:03:58 Establ | 0 0
192.168.80.102 64512 00:04:03 Establ | 0 0
root@master1:/# gobgp global policy
Import policy:
Default: ACCEPT
Export policy:
Default: ACCEPT
Name set-next-hop-self-gpolicy:
StatementName set-next-hop-self-gstmt:
Conditions:
Actions:
Nexthop: self
vagrant@master1:~$ sudo kubectl exec -it loxilb-lb-sk9cd -n kube-system -- bash
root@master2:/# gobgp global
AS: 64512
Router-ID: 192.168.80.11
Listening Port: 179, Addresses: 0.0.0.0
root@master2:/# gobgp neigh
Peer AS Up/Down State |#Received Accepted
192.168.80.1 65101 00:36:18 Establ | 1 0
192.168.80.10 64512 00:36:51 Establ | 0 0
192.168.80.101 64512 00:06:04 Establ | 0 0
192.168.80.102 64512 00:06:06 Establ | 0 0
root@master2:/# gobgp global policy
Import policy:
Default: ACCEPT
Export policy:
Default: ACCEPT
Name set-next-hop-self-gpolicy:
StatementName set-next-hop-self-gstmt:
Conditions:
Actions:
Nexthop: self
loxilb-peer 파드에서는 각각 다음과 같은 BGP 설정을 확인할 수 있습니다:
vagrant@master1:~$ sudo kubectl exec -it loxilb-peer-8bh9b -n kube-system -- bash
root@worker1:/# gobgp neigh
Peer AS Up/Down State |#Received Accepted
192.168.80.10 64512 00:10:35 Establ | 0 0
192.168.80.11 64512 00:10:36 Establ | 0 0
192.168.80.102 64512 00:10:38 Establ | 0 0
vagrant@master1:~$ sudo kubectl exec -it loxilb-peer-f5fmt -n kube-system -- bash
root@worker2:/# gobgp neigh
Peer AS Up/Down State |#Received Accepted
192.168.80.10 64512 00:11:14 Establ | 0 0
192.168.80.11 64512 00:11:12 Establ | 0 0
192.168.80.101 64512 00:11:12 Establ | 0 0
Deploy Services
확인을 위해서 K8s에 TCP, UDP, SCTP 각각 LoadBalancer 타입 서비스를 생성합니다. :
vagrant@master1:~$ sudo kubectl apply -f /vagrant/nginx.yml
service/nginx-lb1 created
pod/nginx-test created
vagrant@master1:~$ sudo kubectl apply -f /vagrant/udp.yml
service/udp-lb1 created
pod/udp-test created
vagrant@master1:~$ sudo kubectl apply -f /vagrant/sctp.yml
service/sctp-lb1 created
pod/sctp-test created
vagrant@master1:~$ sudo kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default nginx-test 1/1 Running 0 19m
default sctp-test 1/1 Running 0 32s
default udp-test 1/1 Running 0 113s
kube-system coredns-77ccd57875-dwrsm 1/1 Running 0 3h2m
kube-system kube-loxilb-5c5f686ccf-knw2p 1/1 Running 0 60m
kube-system local-path-provisioner-957fdf8bc-72kcx 1/1 Running 0 3h2m
kube-system loxilb-lb-9s5qw 1/1 Running 0 72m
kube-system loxilb-lb-sk9cd 1/1 Running 0 72m
kube-system loxilb-peer-8bh9b 1/1 Running 0 29m
kube-system loxilb-peer-f5fmt 1/1 Running 0 29m
kube-system metrics-server-648b5df564-mfg2j 1/1 Running 0 3h2m
다음 명령어로 서비스 생성을 확인하면 external IP를 할당받았음을 확인할 수 있습니다.
vagrant@master1:~$ sudo kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 14m
nginx-lb1 LoadBalancer 10.43.91.80 123.123.123.1 55002:32694/TCP 3m11s
sctp-lb1 LoadBalancer 10.43.149.41 123.123.123.1 55004:31402/SCTP 3m57s
udp-lb1 LoadBalancer 10.43.149.142 123.123.123.1 55003:30165/UDP 3m18s
LoadBalancer 서비스를 생성하면 LoxiLB에도 룰이 생성됩니다. 다음 명령어로 확인할 수 있습니다:
vagrant@master1:~$ sudo kubectl exec -it loxilb-lb-9s5qw -n kube-system -- bash
root@master1:/# loxicmd get lb
| EXTERNAL IP | PORT | PROTOCOL | BLOCK | SELECT | MODE |# OF ENDPOINTS| MONITOR |
|---------------|-------|----------|-------|--------|--------|--------------|---------|
| 123.123.123.1 | 55002 | tcp | 0 | rr |fullnat | 1 | Off |
| 123.123.123.1 | 55003 | udp | 0 | rr |fullnat | 1 | On |
| 123.123.123.1 | 55004 | sctp | 0 | rr |fullnat | 1 | On |
LoxiLB 인스턴스는 생성된 서비스 IP를 peer에도 전달합니다. 클라이언트의 BGP 서버와 Worker 노드의 loxilb-peer는 이러한 advertised된 경로를 등록하기 때문에 해당 정보를 양쪽에서 확인할 수 있습니다.
In the Client:
$ ip route
default via 192.168.20.1 dev eno1 proto static metric 100
123.123.123.1 via 192.168.80.10 dev vboxnet2 proto bird metric 32
169.254.0.0/16 dev eno1 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.20.0/24 dev eno1 proto kernel scope link src 192.168.20.55 metric 100
192.168.80.0/24 dev vboxnet2 proto kernel scope link src 192.168.80.1
192.168.90.0/24 dev vboxnet0 proto kernel scope link src 192.168.90.1
In the Worker node:
vagrant@worker2:~$ ip route
default via 10.0.2.2 dev eth0
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.42.0.0/24 via 10.42.0.0 dev flannel.1 onlink
10.42.1.0/24 via 10.42.1.0 dev flannel.1 onlink
10.42.2.0/24 via 10.42.2.0 dev flannel.1 onlink
123.123.123.1 via 192.168.80.10 dev eth1 proto bgp
192.168.80.0/24 dev eth1 proto kernel scope link src 192.168.80.102
Time to validate the results
다음과 같이 클라이언트는 external 서비스 IP를 사용해서 TCP 서비스에 접근할 수 있습니다:
Conclusion
이 블로그가 여러분에게 Kubernetes 클러스터 내부에 LoxiLB를 배포하는 방법에 대한 좋은 아이디어와 K8s의 클러스터 내 LB 기반 서비스에 대한 흥미로운 정보를 제공했기를 바랍니다. 저희 작업이 마음에 드신다면 Github 페이지로 이동하여 Star 부탁드립니다. Slack 채널을 통해 연락을 주시면 귀중한 피드백과 아이디어를 공유할 수 있습니다.
Note: 이 포스트에서 사용한 모든 스크립트 및 설정은 여기에서 확인할 수 있습니다. 다운로드 후 다음 명령어를 실행하시면 됩니다.
$ ./config.sh
$ ./validation.sh
Note: 모든 설정을 정리하려면 다음 스크립트를 실행하면 됩니다.
$ ./rmconfig.sh
Comments