Loading
2022. 7. 18. 15:44 - lazykuna

terraform 경험기

테라폼에 평상시에 관심이 있었으나 시간도 없고 돈도 들고(?) 해서 쓸 기회가 좀처럼 없는 서비스였는데, 어쩌다 기회가 생겨서 이 참에 한번 사용하게 되었다.

물론 처음 써보는 물건이니만큼 상당한 삽질을 했고, 삽질은 좋은 포스트 거리가 된다. 그래서 다시 키보드를 열심히 두들겨서 잊기 전에 글로 남겨 둔다.

테라폼 플러그인, 그리고 모듈화

먼저 테라폼의 아주 기본적인 기능만 알고 있었던 나는 (resource 구문을 통해서 리소스 생성 등) 보다 구체적인 내용에 대해 알아볼 필요가 있었다.

테라폼 플러그인

테라폼 스크립트는 서비스 프로바이더에 의해 정의되는 구조더라. 그 정보가 이 사이트에 다 올라와 있다. 그리고 terraform init 하면 알아서 플러그인을 가져와서 설치하더라. 이건 좀 편한 듯. 확장성도 좋고.

모듈

아무래도 테라폼 단위를 쪼개다 보면 모듈별로 / MSA별로 스크립트를 나누게 되기 마련이다. 그런데 이걸 어떻게 불러오지? 하다가 보니 module 단위로 만들어서 쓸 수 있더라. 오호라, 이게 테라폼의 또다른 묘미.

모듈간 인풋 / 아웃풋은 variable / output으로 정의하여 쓸 수 있다.

module "a" {
  source = "./a"
  var_a = "1234"
  var_b = 1234
}

module "b" {
  source = "./b"
  var_c = module.a.output_a
  var_d = module.a.output_b
}

… 이런 식으로 사용할 수 있더라.

각 모듈을 만들때마다 반드시 terraform init 을 해줘야 한다는 게 특징이다. ideomptent 해서 여러번 돌려도 별 상관은 없다만… 자동으로 안 해주나..?

비밀 값 & 세팅

두 가지 방식으로 많이 하더라.

  • tfvars 선언하여 별도로 관리 : 간단한 미니 프로젝트에서 쓰기는 좋은데, 규모가 커지면 관리도 힘들고 remote 저장 방식이 아니라는 점이 좀 걸림. 별도로 pull 하는 스크립트라도 짜야..?
  • VAULT 같은 key-value 프로바이더 : 이건 회사에서 쓰는 방식인데, terraform과 integrate 되어서 쓰기 좋더라. 이거 왜 쓰나 했더니 다 이유가 있었구만

VPC와 Subnet, 그리고 Router

먼저 VPC는 가상의 데이터센터라고 볼 수 있다. 나만의 데이터센터가 키보드 몇 번 눌러서 완성이 되는 것이다!

그렇다고 데이터센터 내 컴퓨터들이 모두 연결되어있다는 건 아니다. 이제 망을 구축해야 한다. 그러니까 랜선을 깔아줘야 한 다 이 말. 그 역할은 Subnet이 수행한다.

그런데 하나 빼 먹은게 있다. 지금 이대로는 외부 인터넷과 연결이 안 된다! 따라서 외부 인터넷과 연결할 랜선을 하나 들고와야 한다. 그게 바로 internet gateway 이다.

아무튼 대충 망을 깔았다고 치자. 아무튼 컴퓨터에 랜선들이 잔뜩 꽃혔지만 여전히 하나 빼먹은게 있다. 바로 데이터를 어디로 보내냐의 문제이다. 이 때 라우터가 필요하다. VPC와 subnet을 만들 때 ip 대역을 지정해 놨으니, 그 대역에 따라서 어떤 subnet을 타게 할 지를 라우터에 선정하면 인프라 구축은 마무리가 된다.

resource "aws_route_table" "public_route" {
  vpc_id = aws_vpc.cluster_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.cluster_igw.id
  }

  tags = {
    Name = "${var.app_name}-route-table"
  }
}

resource "aws_route_table_association" "to-public" {
  count          = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public_route.id
}

직접 route에 public subnet의 cidr_block을 지정할 수 있는 것 같긴 한데 이쪽이 범용적인 방법인 듯…. private subnet을 만들거면 internet_gateway route만 빼면 된다.

특이한 점은, subnet의 경우 사용 가능한 region이 한정되어 있었다는 것이다. 정확히는 보통 aws에서 region을 us-west-2 식으로 지정하면, subnet은 us-west-2a ~ us-west-2d 식으로 배정된다는 것이었다. 고가용성을 위함이라고 하던데, 그래도 저렇게까지 구성할 필요가 있나 싶기도 한 생각이… 아마 이건 인프라를 제대로 해보지 않은 내 얄팍한 지식의 한계일 듯.

혹시 VPC의 region에 dependent 한 걸까? 아직 정확한 내용은 못 찾았지만 아마 그럴 듯 싶다.

여튼 정리하면, 내가 이해한 바로는 VPC/Subnet/Router은 사이버 데이터센터 구축 요소에 가깝다고 보여진다.

AWS: 컴퓨터는 알아서 들여 놔 줄테니, 데이터센터 이름 지어주고, 망 깔아주고, 라우터 깔아주세요~

VR로 구성할 수 있게 만들면 재밌겠다

참고

https://dontbesatisfied.tistory.com/13

Security Group은 또 뭐냐?

열심히 라우터도 깔고 망도 깔아서 어찌어찌 데이터센터를 꾸리는 데까지 성공했다고 치자! 하지만 이렇게 열심히 꾸려놓은 데이터센터에 아무나 접속을 할 수 있도록 하면 상당히 곤란할 것이다. 그래서 Security Group이 존재한다.

하지만 보안의 AWS가 아니겠는가? 따라서 Security Group은 어떠한 Resource의 경우든 필수로 들어간다고 보면 된다. 이거 세개는 필수다!

  • VPC (어떤 데이터센터에 있을 건지)
  • Subnet (어떤 망에 랜선을 꽃을 건지)
  • Security Group (방화벽)

여기서 문제는 Security Group을 깜빡하게 세팅 안 했을 때다. 그러면 기본값으로 security group을 aws가 만들어 버릴때가 있는데, 이게 상당히 골아프다.

기본 보안 그룹의 소스(cidr)을 보면 0.0.0.0이 아니라 이상한 이름이 있다. 이거 자기 자신이다. 그러니까 자기 자신이 보낸 요청을 자기자신이 모두 수락한다? 이게 뭔 소리야… 접속이 안되면 십중팔구는 이 문제니까 잘 알아두면 좋을지도…

아무튼 모두가 이런 세팅 값은 의도하지 않을테니, 저 세가지 요소는 잘 알아두고 미리미리 세팅을 염두에 두어야 함.

다른 security group에서의 라우팅

참고로 다른 security group에서 라우팅이 될 경우, 해당 트래픽에 대해서 ingress 룰을 또 줄 수가 있다. nested 된 것처럼 보이는 security_groups 용도가 이것.

resource "aws_security_group" "test" {
  ingress {
    protocol        = "tcp"
    from_port       = var.host_port
    to_port         = var.container_port
    cidr_blocks     = ["0.0.0.0/0"]
    security_groups = [var.aws_lb_id]
  }

  ...
}

그런데 이러면 레코드가 두개 만들어지네. 원래 이런 의도가 맞나..?

참고

https://velog.io/@noname2048/aws-rds를-만들었는데-접속이-안된다

ECS와 ECR

사실 처음에 EKS만 쓸까 생각했었는데, ECS가 있더라. 람다처럼 쓴 만큼만 컨테이너 돌려서 청구한댄다. 도커 이미지를 그대로 쓸 수 있기도 하여 얼마든지 EKS로 돌아갈 수 있기도 하고, 아직 production에 들어가지 않은 지금 상황에서는 아주 꿀이다! 주저 없이 바로 ECS deploy를 했다.

보니까 ECS는 ECR이라는 이미지 저장소에서 이미지를 deploy 한다고 했다. 여러 의구심이 들었다.

  • 이미지 없이도 ECS 정의가 돼?
  • ECR에서 어떤 이미지를 찾아 쓸 지 알 수 있어?

라고 했지만 정작 ECS 와 ECR은 docker처럼 단순한 1:1 매칭으로 해 놓으면 다 똑같더라. 그리고 ECR에 이미지 업로드하면 ECS가 알아서 deploy 한다. 이것 상당히 편리.

… 그럼에도 불구하고 ECS 구성요소 꽤 복잡한 게 많다. ECS 각 디플로이는 task로 취급되고, 그래서 ECS 디플로이 상태 보려면 테스크로 일일이 들어가 봐야 하고, 태스크를 만드는 방법은 클러스터 안에 있는게 아니라 “테스크 정의"라고 따로 있고… 왜 이렇게 복잡한 건지.

그리고 ECR도 올리려면 추가 인증 하고, 기존 이미지 태그 바꾸고 쇼를 해야 한다. ECR의 푸시 명령 보기를 보면 일련의 명령어가 나온다. 어우 귀찮아…

가장 큰 치사한 단점은 deploy 하는 것도 가격에 포함된다. 이 … 쓴만큼만 낸다면서… deploy도 사용량에 넣는건 치사한거 아니냐??

또 신기한게 ECS task마다 ip가 생긴다는 점이다. 없으면 pod deploy를 못 한다! 보니까 eip (Elastic IP)를 알아서 매번 새로 생성하긴 하던데 (이것도 특이함), 이러면 낭비가 큰 거 아닌가? deployment 때문에 공인 ip가 필요하다던데, 그것 때문인가? 없어도 VPC, subnet 세팅 되어 있으면 된 거 아닌가? 으음… 이래저래 잘 모르겠다.

  • 없어도 물론 deploy 가능한 수단이 있긴 한데, privateLink 구성하고 해야 해서 꽤 귀찮다 카더라. 그리고 애당초 public subnet인데도 필요해..?

Service Discovery는 또 따로야?

k8s… 아니 거기까지 안가고, 그냥 docker만 해도 service discovery를 자동으로 해 준다. container name에 맞춰서 redirect 해 주도록 되어 있다. ECS도 그룹만 맞춰주면 되지 않을까? 생각했다.

응 아니야~ CloudMap 서비스 써야 한다.

직접 service_discovery namespace와, service_discovery service 만들어 줘야 한다.

# Cloud map namespace for service discovery
resource "aws_service_discovery_private_dns_namespace" "ecs" {
  name = var.service_name
  vpc  = aws_vpc.cluster_vpc.id
}

resource "aws_service_discovery_service" "ecs" {
  name = var.app_name
  dns_config {
    namespace_id   = var.aws_service_discovery_namespace_id
    routing_policy = "MULTIVALUE"
    dns_records {
      ttl  = 10
      type = "A"
    }
  }
  health_check_custom_config {
    failure_threshold = 5
  }
}

아니 서비스 디스커버리 안 쓰고 ECS pod 쓰는 놈이 어디있다고 이따구로 만들어놨냐 쒸익쒸익

이거 하고 VPC에서 enable_dns_hostnames = true 켜줘야 한다. 안 그러면 redirect 안 시켜줌…

재미있는 건, 이렇게 만들어 놓으면 알아서 Route53에 private DNS가 생기더라. 그리고 DNS의 특징 답게.. 바로 redirect 안 시켜주고 조금 지나서 네임서버에 정보 업데이트 될 때까지 기다려야 한다.

도메인 리다이렉션과 LoadBalancer

도메인 리다이렉션은 간단했다. 그냥 A 레코드로 LB에 넘겨주더라. 알아서 LB가 어떤 레코드가 들어올 줄 알고, 처리하는 형태로 되어 있는 듯.

특이한 점은 LoadBalancer 쪽에 있었다.

  • ELB 기능이 EC2에 있네?
    • elb 기능 자체가 ec2 클러스터를 써서 그런가? 별도 서비스가 아니라 EC2에 붙어있는 게 신기 🤔
  • ELB는 왜 자기 도메인을 가지고 있는 걸까?

항상 보면 ELB는 DNS 연결 안해도 자체 도메인이 있더라. 왜 그럴까? 일단 ELB 했으니까 테스트라도 할 수 있게 엔드포인트를 주는 걸까? 클러스터니까 ip 그대로 주면 곤란한 것도 있고? 찾아봐도 답은 딱히 없었다. 진실은 AWS 관계자만이…

이 외 자잘한 문제들

이외에도 테라폼을 쓰다 보니 이상한 문제들이 발생한 게 꽤 있는데, 역시 IaC는 코딩보다는 쉽지 않다. 내 코드는 옳기에 validate / plan에서 제대로 동작하더라도, 막상 서비스 프로바이더에서 막히는 경우도 꽤 있음. (당장 내 회사만 해도 문제 있을 때 있으니 ㅎㅎ;)

MongoDB는 안 되네…

AWS에서 만든 서비스가 아니라 그런지 리소스에 없다. 당연히 있을 거라고 생각한 내가 바보…

그래도 찾아보니 MongoDB Atlas에서 terraform 지원하더라. 그걸로 쓰기로 했다.

MongoDB Atlas에서 allow ip 설정하니 서버가 반응을 안 함

이건 서비스 프로바이더 버그 같다. mongodbatlas_project_ip_access_list 쓰면 서버에 값은 설정되는데 클라이언트로 회신이 안 오는 것 같다.

실수로 리소스 지움 😨

이러면 terraform state와 mismatch가 발생해서 리소스 변화를 줄 수 없는 경우가 생긴다.

이건 terraform refresh 해서 해결되는 경우도 있다는데 난 아니었다. 또 MongoDB Atlas 너야? remote 객체를 지우고, terraform.tfstate 들어가서 직접 파일을 수정해서 겨우 동기화시켰다 😅

RDB SG 변경시 생기는 이상한 문제

RDS security group 변경 후 다시 바꾸려고 했더니, 이상한 오류가 발생한다.

╷
│ Error: modifying DB Instance rds: InvalidDBSecurityGroupState: Cannot revoke vpc security group membership because it is not in the authorized state.
│     status code: 400, request id: c1c5fa4a-52f2-4518-9415-996cd5a0ff52
│
│   with module.db.aws_db_instance.rds_instance,
│   on db/main.tf line 2, in resource "aws_db_instance" "rds_instance":
│    2: resource "aws_db_instance" "rds_instance" {
│
╵

찾아봐도 내용이 없어서 웹을 들어가보니 무언가 이상한 일이 일어나고 있었다..

뭐야 이거 왜이래? 심지어 거진 반나절이 지났는데도 안 바뀌고 있다. 이거 고장난건가…

아무래도 AWS 센터에다가 문의해봐야 할 듯. 기다리면 고쳐지나?

느낀점

생각보다는 IaC를 “code” 만으로 할 수는 없구나

다양한 문제를 맞닥뜨리면서, 이를 해결하기 위해 시간을 많이 소모한 곳은 코드가 아니라 AWS 콘솔 창이었다. 어찌되었든 콘솔에 들어가서 내가 원하는 대로 세팅이 잘 되었는지, 의도하지 않은 세팅 값이 있었는지, 있었다면 그 값의 세팅은 어땠는지 일일이 확인해줘야 했다. 이런 점에서는 아직 테라폼이 무안단물은 아니구나 생각이 들었다.

사실 terraform plan 쳤을 때 나왔을 수도 있다. 하지만 그걸 일일이 읽을만한 자질이 아직 나한테는 없었다… 글쎄, 이건 짬이 좀 더 차면 몇 개 속성정도는 눈여겨 볼 수 있게 되려나?

그래도 consistency는 유지된다

그럼에도 불구하고 IaC의 아주 큰 장점은 state를 보장한다 는 점이 큰 것 같다. 시간이 더 든다고는 했지만, 역설적이게도 저렇게 복잡한 인프라를 손으로 직접 구성하려면 절차나 상태를 일정하게 구성하기는 쉽지 않을 것이다. 코드로 짜니까 항상 동일한 인프라 구축을 기대할 수 있을 것이다. 이건 절차 기반으로 진행하는(ansible이나, 재래식 손/마우스 세팅...) 기존 방식의 인프라 구성에 비해서는 확실히 혁신이 맞다.

코드는 거짓말을 하지 않는다.

'개발 > Infra' 카테고리의 다른 글

AWS 호스팅 및 메일 설정기  (0) 2022.07.26
sendmail 서버 구성 삽질기  (0) 2022.06.27
AWS를 이용하여 클라우드 아키텍처 구성하기  (0) 2022.05.08