EKS 503 탈출기

EKS를 처음 구축하고 나를 반겨준 화면은 단연 503 Service Unavailable 이었다. ㅠㅠ 문제의 원인은 Pod, Service, Ingress 혹은 이미지 자체에 있을 수 있다.

문제점을 찾기 위해서 아래와 같은 순서대로 확인했다.

Pod가 정상 동작 중인지 확인

먼저 pod가 동작 중인지 확인한다.

1
2
3
4
5
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
shipda-api-v2-7b45cdf6df-7hfsk 1/1 Running 0 2d12h
shipda-api-v2-7b45cdf6df-b8hzq 1/1 Running 0 2d12h
shipda-api-v2-7b45cdf6df-v24vh 1/1 Running 0 2d12h

뭐 다들 정상 동작중이니 이미지를 확인해본다.

이미지 검사

Pod에 올라간 도커 이미지가 진짜로 제대로 도는지 확인해본다. 다만 이는 Health Check를 잘 만들어놨다면 좀 쉽게 체크할 수 있다.

1
kubectl port-forward pod/shipda-api-v2-7b45cdf6df-7hfsk 8080:8080

해당 Pod의 8080포트를 localhost 8080 포트에 바인딩시킨다. 그 후 로컬호스트의 8080포트에 대고 API를 테스트해본다.

여기까지 문제가 없으면 Service 혹은 Ingress가 문제다.

서비스

원인은

  1. 인그레스가 서비스를 못찾거나
  2. 서비스가 Deployment 를 못찾거나

이 둘 중 하나로 좁혀졌다. 하지만 어디서 문제인지 도통 알 수가 없었다. 그래서 옆에 곰돌이를 앉혀놓고 다시 한 번 서비스를 확인해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl describe service -n shipda
Name: shipda-api-v2
Namespace: shipda
Labels: <none>
Annotations: kubectl.kubernetes.io/
Selector: run=shipda-api-v2
Type: NodePort
IP: 10.100.75.73
Port: <unset> 80/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30888/TCP
Endpoints: 172.31.1.205:8080,172.31.21.125:8080,172.31.39.55:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

deployment는

1
2
3
4
5
6
7
8
9
apiVersion: apps/v1
kind: Deployment
metadata:
name: shipda-api-v2
labels:
app: shipda-api-v2
namespace: shipda
spec:
...

곰돌이에게 deployment의 metadata를 설명하다가 갑자기 뜨얽!
Deployment에서는 label을 app: shipda-api-v2 로 해놨는데 Service Selector는 run: shipda-api-v2 로 해놓았던 것이었다!

따라서

잉그레스 -> 서비스 -> 디플로이먼트

이렇게 흘러가야할 트래픽이 서비스에서 디플로이먼트로 흘러가지 못해서 503에러가 발생했다. 왜 selector를 run으로 한지 이유는 모르겠지만…

같은 VPC인데도 EKS에서 RDS 연결 안될 경우

AWS의 Kubernetes 서비스인 EKS를 구성할때 겪었던 일이다.
EKS와 RDS를 같은 VPC 안에 두었음에도 불구하고 EKS에서 RDS로 연결이 되지 않고 계속해서 타임아웃에러만 발생했다.

이 경우 먼저 Pod에서 RDS 주소를 인지하는지 먼저 확인했다.
먼저 Pod 이름 먼저 확인

1
$ kubectl get all -n shipda

파드 이름이 pod/shipda-api-v2-79d868d65-2kh4t 이라 한다면 pod에다 nslookup 명령어를 실행시킨다.

1
2
3
4
5
6
7
8
9
10
11
$ kubectl exec -it pod/shipda-api-v2-79d868d65-2kh4t -n shipda nslookup <blahblah>.rds.amazonaws.com
Server: 10.100.0.10
Address: 10.100.0.10:53

Non-authoritative answer:
<blahblah>.rds.amazonaws.com canonical name = <blahblah>.ap-northeast-2.compute.amazonaws.com
Name: <blahblah>.ap-northeast-2.compute.amazonaws.com
Address: 111.111.111.111

Non-authoritative answer:
<blahblah>.rds.amazonaws.com canonical name = <blahblah>.ap-northeast-2.compute.amazonaws.com

DNS 를 리졸빙 하는걸로 봐서는 DNS는 문제 없는것 같다. 그러면 실제로 접속이 되는지 핑을 날려본다.

1
$ kubectl exec -it pod/shipda-api-v2-79d868d65-2kh4t -n shipda ping <blahblah>.rds.amazonaws.com

핑이 가지 않았다. 해결 방법을 한참 찾았다. 같은 VPC라서 문제 없을 줄 알았는데 뒤통수 맞은 기분이었다. 사실 프로덕션으로 GCP와 Azure는 썼었는데 AWS는 처음이라 좀 더 헤맸을수도 있다. 사실 요즘 클라우드가 다들 엇비슷해서 있을거 (거의) 있어서 적응에는 큰 문제는 없지만 아마존 같은 경우엔 VPC, Security Group, Subnet, RoutingTable 등을 좀 더 세밀하게 지정해야 하기 때문에 헤맸을 수도 있다.

결국 해결책은 -

RDS의 시큐리티 그룹에 EKS의 시큐리티 그룹을 넣어주면 해결되는 일이었다.

뭔가 굉장히 허탈하다. 참고로 다른 VPC라면 VPC 피어링을 사용하면 된다.

go 언어 구글 클라우드 Datastore 패키지를 잘 씁시다!

3월 초에 시간이 좀 많이 나서 펫 프로젝트를 진행한 일이 있다. 구글 클라우드에서 AppEngine Standard Environment + Datastore으로 프로젝트를 구성했다. AppEngine은 복습할 겸 회사에서 지겹도록 파이썬을 써서 Go언어로 만들었다. API는 쉽게 구현할 수 있었다. 다만 문제는 속도가 너무 느렸다. 아래는 대략적인 코드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package mypetproject

import (
"context"
"net/http"

"github.com/gorilla/mux"
"cloud.google.com/go/datastore"
"google.golang.org/appengine"
)

func handleTable(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)

dsClient, err := datastore.NewClient(ctx, "my-project")
if err != nil {
// Handle error.
}
k := datastore.NameKey("Entity", "stringID", nil)
e := new(Entity)
if err := dsClient.Get(ctx, k, e); err != nil {
// Handle error.
}
// ...
}

func init() {
r := mux.NewRouter()
r.HandleFunc("/table", handleTable).Methods("GET")

http.Handle("/", r)
}

하나의 Kind(관계형DB의 Table)에 몇 개 되지도 않은 Entity(관계형DB의 Row)만 있고 단순한 GET 만을 했을 뿐인데 속도가 600~800ms 나 걸렸다! 제일 의심이 가던 부분은 datastore.NewClient()였다. 이 함수에서 Datastore에 연결하는데 오래 걸리리라 생각했다. 그래서 이를 패키지 전역으로 빼려고 했으나 실패. datastore.NewClient()의 인자로 context를 받는데, 이 context는 appengine의 context 를 받아야 했다.

1
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)

구글이 이렇게 허접하게 만들었을리 없는데, 뭔가 다른 방법이 있지 않을까 하고 낑낑대던 와중에 눈에 띈 패키지명이 있었다. 바로 google.golang.org/appengine/datastore … 내가 사용했던 Datastore 패키지와 달랐다.

정리하자면

google.golang.org/appengine/datastore는 Google App Engine Standard Environment에서 사용하는 패키지다. godoc에서 확인해보면 맨 첫 머리에 아래와 같은 설명이 있다.

Package datastore provides a client for App Engine’s datastore service.

그렇다. google.golang.org/appengine/datastore 패키지는 App Engine Standard Environment 전용이며, 그 외에 로컬이나 Compute Engine, App Engine Flexible Environment에서 사용할 때는 cloud.google.com/go/datastore 를 사용해야 한다.

App Engine에서 부를때는 Client를 만드는 부분, 즉 인증, 연결 부분이 생략되어 있기 때문에 코드가 다음과 같이 조금 더 간단해졌다. 또한 응답시간 역시 기존 600~800ms에서 모두 80ms 아래로 떨어뜨릴 수 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package mypetproject

import (
"context"
"net/http"

"github.com/gorilla/mux"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
)

func handleTable(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)

k := datastore.NewKey(ctx, "Entity", "stringID", 0, nil)
e := new(Entity)
if err := datastore.Get(ctx, k, e); err != nil {
http.Error(w, err.Error(), 500)
return
}
// ...
}

func init() {
r := mux.NewRouter()
r.HandleFunc("/table", handleTable).Methods("GET")

http.Handle("/", r)
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×