Intro
- Kubernetes는 API 서버 입니다. 저희가 kubectl을 이용해 kubernetes로 명령을 보내는 것은 사실 kubernetes의 REST API 서버로 요청을 보내고 응답을 받아오는 것입니다.
- 이번 글에서는 Kubernetes에 REST API 요청을 보내고 응답을 받아오는 방법과 Spring의 RestTemplate를 통해 이를 구현하는 방법을 살펴보겠습니다.
- Kubectl get pod 명령을 Spring 어플리케이션에서 REST API로 구현하는 것을 목표로 합니다.
- Kubernetes는 Minikube로 간단하게 구현했습니다. https://minikube.sigs.k8s.io/docs/start/를 참고부탁드립니다.
사전 작업
REST API 요청을 위한 JWT Token 발급
cat $HOME/.kube/config
apiVersion: v1
clusters:
- cluster:
certificate-authority: /Users/gwon/.minikube/ca.crt
extensions:
- extension:
last-update: Mon, 25 Apr 2022 23:04:12 KST
provider: minikube.sigs.k8s.io
version: v1.25.2
name: cluster_info
server: https://192.168.59.100:8443
name: minikube
...
- Kubernetes는 REST API 서버로 우리가 흔히 실행하는 명령어인 kubectl get pod, create pod, delete pod 등은 REST API로 구현 가능합니다.
- $HOME/.kube/config에 존재하는 kubeconfig에는 API 서버, 인증 정보가 담겨져 있습니다.
- 로컬에 구성한 쿠버네티스 클러스터의 kubeconfig를 확인해 쿠버네티스 클러스터의 주소 https://192.168.59.100:8443 을 확인했습니다.
kubectl get pods -v 8
I0425 23:06:59.868327 24208 loader.go:372] Config loaded from file: /Users/gwon/.kube/config
I0425 23:06:59.871086 24208 cert_rotation.go:137] Starting client certificate rotation controller
I0425 23:06:59.877122 24208 round_trippers.go:463] GET https://192.168.59.100:8443/api/v1/namespaces/default/pods?limit=500
I0425 23:06:59.877137 24208 round_trippers.go:469] Request Headers:
I0425 23:06:59.877146 24208 round_trippers.go:473] Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json
I0425 23:06:59.877152 24208 round_trippers.go:473] User-Agent: kubectl/v1.23.6 (darwin/amd64) kubernetes/ad33385
I0425 23:06:59.887067 24208 round_trippers.go:574] Response Status: 200 OK in 9 milliseconds
I0425 23:06:59.887091 24208 round_trippers.go:577] Response Headers:
I0425 23:06:59.887102 24208 round_trippers.go:580] Cache-Control: no-cache, private
I0425 23:06:59.887111 24208 round_trippers.go:580] Content-Type: application/json
I0425 23:06:59.887117 24208 round_trippers.go:580] X-Kubernetes-Pf-Flowschema-Uid: 7be26494-6c94-4cdc-ac87-3945e76e4328
I0425 23:06:59.887122 24208 round_trippers.go:580] X-Kubernetes-Pf-Prioritylevel-Uid: fea539d0-00f1-44fe-bc79-5d749f427d70
I0425 23:06:59.887129 24208 round_trippers.go:580] Content-Length: 2932
I0425 23:06:59.887134 24208 round_trippers.go:580] Date: Mon, 25 Apr 2022 14:06:59 GMT
- kubectl -v 8 태그를 통해 명령어의 REST API URL 정보를 확인할 수 있습니다.
- kubectl get pods는 IP:PORT/api/v1/namespaces/default/pods URL로 GET Method 를 지닌 Request를 보낸 결과를 출력하는 명령어 입니다.
- 리눅스의 curl 명령어를 통해 kubectl을 사용하지 않고 pod의 정보를 조회해보겠습니다.
kubectl create clusterrolebinding default-cluster-admin --clusterrole cluster-admin --serviceaccount default:default
TOKEN=$(kubectl get secret $(kubectl get sa default \
-ojsonpath="{.secrets[0].name}") \
-ojsonpath="{.data.token}" | base64 -d)
echo $TOKEN
curl -k -H "Authorization: Bearer $TOKEN" [https://192.168.59.100:8443](https://192.168.59.100:8443)
- Kubernetes에 REST API 요청을 보내기 위해서는 인증을 진행해야합니다. kubectl은 kubeconfig를 통해 인증을 진행했는데 curl 명령어는 kubeconfig를 사용할 수 없기 때문에 Bearer HTTP Authentiation을 통해 인증을 진행하겠습니다.
- Bear HTTP Authentication : 서버로부터 token을 발급받고, 이후 요청 시 Authorization 헤더에 토큰을 실어 보냅니다.
- kubernetes는 기본적으로 default라는 ServiceAccount를 제공하며 ServiceAccount에서 JWT TOKEN 추출 가능합니다.
- default Service Account에 모든 권한을 부여하고 JWT TOKEN을 추출하겠습니다.
curl -H "Authorization: Bearer $TOKEN" <IP>:<PORT>
를 통해 인증을 한후 Kubernetes 서버로 명령을 내릴 수 있습니다.
export TOKEN= value
curl -k -X GET -H "Authorization: Bearer $TOKEN" [https://192.168.59.100:8443](https://192.168.59.100:8443)/api/v1/namespaces/default/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "712"
},
"items": []
}%
- 위 과정에서 추출한 jwt 토큰 값을 환경변수로 저장했습니다.
- https 통신을 진행하기 위해서는 인증서를 검증하는 작업이 진행되야 하는데 로컬 클러스터에서는 서버 자체에서 발급한 인증서이기 때문에 검증이 불가능 합니다. curl의 -k 옵션을 통해 인증서 검증 작업을 생략하겠습니다.
- -X 옵션을 통해 GET METHOD임을 지정했습니다.
- curl의 -H 옵션을 통해 헤더에 추출한 토큰값을 넣어주었습니다.
- IP:PORT/api/v1/namespaces/default/pods로 요청을 보내면 json 형태로 pod의 정보들이 반환됩니다.
Spring Application
- Spring 에서는 RestTemplate를 사용해 REST API 요청을 보낼 수 있습니다.
- RestTemplate를 사용해 kuberentes로 요청을 보내 pod 정보들을 조회해보겠습니다.
Configuration
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyManagementException {
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
HttpComponentsClientHttpRequestFactory customRequestFactory = new HttpComponentsClientHttpRequestFactory();
customRequestFactory.setHttpClient(httpClient);
return builder.requestFactory(() -> customRequestFactory).build();
}
}
Util
@Component
@RequiredArgsConstructor
public class RestApiUtil {
@Value("${config.kubernetes.url}")
private String API_SERVER;
@Value("${config.kubernetes.token}")
private String API_TOKEN;
private final RestTemplate restTemplate;
private final String API_URL = "/api/v1/namespaces/default/";
public static final String RESOURCE_TYPE_POD = "pods";
public ResponseEntity execute(HttpMethod httpMethod, String resourceType) {
String url = API_SERVER + API_URL + resourceType;
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(API_TOKEN);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity(headers);
return restTemplate.exchange(url, httpMethod, httpEntity, Map.class);
}
}
- Kubernetes로 요청을 보내기 위해 RestTemplate에 적절한 값을 넣어주기 위해 RestApiUtil을 생성했습니다.
- @Value를 통해 yaml 값에 서버의 url 주소와 Token 값을 넣어 타인에게 노출되지 않도록 안전하게 보호하겠습니다. - 해당 값은 /main/resources/application.yaml 에 넣어줍니다.
- Configutation에서 만든 RestTemplate을 private final과 RequiredArgsConstructor를 통해 의존성을 주입 받았습니다.
- HttpEntity는 Http Header, Http Body로 이루어져 있습ㄴ다. GET 요청에는 Body가 따로 필요 없기 때문에 HttpHeader를 통해 토큰값과 content-type 값을 지정해주고 HttpEntity를 생성했습니다.
- restTemplate.exchange()는 요청을 보내고자 하는 url, httpMethod, httpEntity, 반환 타입을 인자로 받습니다.
Service
@Service
@Transactional
@RequiredArgsConstructor
public class ApiService {
private final RestApiUtil restApiUtil;
public List getResource(String resourceType) {
Map response = (Map)restApiUtil.execute(HttpMethod.GET,resourceType).getBody();
List<Map> items = (List) response.get("items");
return items.stream()
.map(item -> {
Map metadata = (Map)item.get("metadata");
String name = (String)metadata.get("name");
String namespace = (String)metadata.get("namespace");
Map status = (Map)item.get("status");
String phase = (String)status.get("phase");
return PodDto.builder()
.name(name)
.status(phase)
.namespace(namespace)
.build();
})
.collect(Collectors.toList());
}
- RestApiutil의 execute 함수에 GET Method, resourceType를 인자로 넘겨 해당 resource의 정보들을 가져오는 REST API를 호출합니다.
- 가져온 데이터들은 responseEntity 형식으로 반환되는데 getBody()함수를 통해 body의 값을 Map 형태로 받아 옵니다.
- response 데이터는 Map<String,LinkedHashMap> 형식으로 넘어오게 됩니다.
- 인텔리제이의 디버깅 기능을 사용해 items의 값을 살펴본 결과 입니다.
- java의 동적 캐스팅을 사용합니다.
- item List에서 스트림을 사용해 각 아이템마다 필요한 정보를 추출한 후 Dto로 변환시켜 다시 리스트로 만듭니다.
Dto
@Getter
@Setter
@NoArgsConstructor
public class PodDto {
private String name;
private String status;
private String namespace;
@Builder
public PodDto(String name, String status, String namespace) {
this.name = name;
this.status = status;
this.namespace = namespace;
}
}
- Pod 정보를 담아 반환할 Dto를 정의했습니다.
Controller
@RestController
@RequiredArgsConstructor
public class ApiController {
private final ApiService apiService;
@GetMapping("/pods")
public List<PodDto> getPods(){
return apiService.getResource(RESOURCE_TYPE_POD);
}
}
- apiService를 의존성 주입 받습니다.
- localhost:8080/pods 로 GET 요청이 들어오면 service의 getResource를 실행한 결과값을 반환합니다.
- ResponseEntity를 이용해 Http 응답을 반환해줘야하지만 이번 글에서는 Kubernetes로 RestAPI를 보내는것을 설명하기 위해 간단하게 구현한 프로젝트이기 때문에 Dto를 바로 반환하게 만들어주었습니다.
실행
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 27s
curl http://localhost:8080/pods
[{"name":"nginx","status":"Running","namespace":"default"}]
- Spring Application을 통해 kubectl get pods 명령을 구현했습니다.
Reference