포스트

AWS Site-to-Site VPN을 통한 하이브리드 클라우드 구축 프로젝트


2024년 7월 8일 ~ 2024년 8월 5일까지 진행한 하이브리드 클라우드 구축 프로젝트 진행과정 중 일부를 포트폴리오로 제출하기 위해 작성되었습니다.

팀 구성원으로는 팀장 고민혁, 김성훈, 김세벽, 오주화 총 4명이서 진행하였습니다



목차


  1. 개요

  2. Terraform AWS 리소스 생성
    2-1. VPC 및 VPN 생성
    2-2. AWS EKS Addons

  3. VPN 연결
    3-1. IPSec 구성 및 자동화
    3-2. FRR(Free Range Routing) 구성 및 자동화

  4. 테스트

  5. Trouble Shooting
    5-1. 서버와 시스템 시간 불일치 문제
    5-2. EKS 클러스터 생성 권한 미지정 문제
    5-3. AWS 라우팅 테이블 미등록 문제


1. 개요


프로젝트명은 티켓 예매 서비스 확장을 위한 하이브리드 클라우드 환경 설계 및 구축입니다. 프로젝트에서 사용한 소프트웨어 스택입니다.

프로젝트 소프트웨어 스택 프로젝트 소프트웨어 스택


시나리오 고객의 첫 요구사항이 온프레미스 쿠버네티스 환경으로의 전환이었고, 요구사항을 반영한 저희의 솔루션인 온프레미스 쿠버네티스 아키텍처입니다.

온프레미스 쿠버네티스 아키텍처 온프레미스 쿠버네티스 아키텍처


쿠버네티스 솔루션 이후 프로젝트 시나리오에서 급격한 트래픽으로 인해 다운타임이 발생하였고, 다운타임 재발 방지를 위해 온프레미스 쿠버네티스에서 AWS EKS로 마이그레이션을 결정하였습니다.

만약 온프레미스 쿠버네티스 시스템을 확장한다면, 추가적인 비용과 시간이 소요되는 점과 그동안 안정적인 서비스 제공이 어려울 수 있기 때문입니다.

단, 온프레미스의 DB 클러스터는 유지하기로 결정하였는데, 그 이유는 이미 투자된 비용이 있다는 점과 ProxySQL을 활용해 트랜잭션을 읽기와 쓰기로 분리하여 DB 클러스터에 가해지는 부하를 분산하고 있으며, OrchestratorDB 토폴로지 시각화와 함께 장애 복구 조치가 구성되어 있기 때문입니다.


위의 사항들을 고려하여 구성한 최종 아키텍처는 다음과 같습니다.

하이브리드 클라우드 아키텍처 하이브리드 클라우드 아키텍처


AWS의 대부분의 리소스는 Terraform을 통해 프로비저닝하였으며 상세 정보는 .tfstate에 저장됩니다. 온프레미스의 DB 클러스터를 유지하겠다는 결정에 따라 AWS Site-to-Site VPN을 활용해 온프레미스와 AWS VPC를 연결합니다.

Customer Gateway는 VPN 서버를 의미하며, VPN 서버는 IPSec 구성과 라우팅 설정 등을 Ansible을 통해 자동화를 진행하였습니다.

IPSec 구성 정보를 정의하는 데 사용된 도구는 Libreswan이며, 프로젝트 진행 당시 물리적인 네트워크 장비에 접근할 수 없었기 때문에 FRR(Free Range Routing) 도구를 사용해 소프트웨어 라우팅을 구현하였습니다.

소프트웨어 라우팅을 진행할 때, 동적 라우팅 프로토콜인 OSPF 프로토콜과 BGP 프로토콜을 통해 서로 다른 네트워크인 온프레미스 네트워크와 AWS VPC 간 통신이 원활하게 이루어질 수 있도록 하였습니다.

마지막으로, 팀원 중 CI/CD 개발자가 EKS에서 빌드와 배포를 진행할 때, Load Balancer Controller라는 특정 파드가 호스팅 영역에 속하는 ALB(Application Load Balancer)를 생성하면 External-DNS 파드가 AWS Route 53에 DNS 레코드를 자동으로 등록할 수 있도록 구성하였습니다.


2. Terraform AWS 리소스 생성


2-1. VPC 및 VPN 생성


먼저, 가장 기본적인 VPC 구성정보에 대한 Terraform 코드입니다.

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
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = local.cluster_vpc_name

  cidr            = var.network_cidr # 172.20.0.0/16
  azs             = slice(data.aws_availability_zones.available.names, 0, 3)
  private_subnets = [cidrsubnet(var.network_cidr, 8, 12), cidrsubnet(var.network_cidr, 8, 24), cidrsubnet(var.network_cidr, 8, 36)]
  public_subnets  = [cidrsubnet(var.network_cidr, 8, 112), cidrsubnet(var.network_cidr, 8, 124), cidrsubnet(var.network_cidr, 8, 136)]
  # private_subnets = ["172.20.12.0/24",  "172.20.24.0/24",  "172.20.36.0/24"]
  # public_subnets  = ["172.20.112.0/24", "172.20.124.0/24", "172.20.136.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  propagate_public_route_tables_vgw  = true
  propagate_private_route_tables_vgw = true

  public_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/elb"                      = 1
  }

  private_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb"             = 1
  }
}

온프레미스의 네트워크 대역은 192.168.2.0/24 대역이며, 위의 Terraform 코드에서 AWS VPC 네트워크 대역은 172.20.0.0/16입니다.

위 코드 중 propagate_public_route_tables_vgw 필드와 propagate_private_route_tables_vgw 필드의 값을 true로 설정함으로써 AWS VPC의 퍼블릭 및 프라이빗 라우팅 테이블에 온프레미스 네트워크 정보가 자동으로 등록될 수 있습니다.


위 코드로 생성된 VPC 아래에서 EKS 클러스터가 생성됩니다.

다음으로는 VPN과 관련된 Terraform 코드입니다.


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
33
34
35
36
resource "aws_customer_gateway" "tc-vpc-cgw" {
  depends_on = [module.vpc]
  bgp_asn    = 65000
  ip_address = var.onpremise_ip # 온프레미스 외부 공인 
  type       = "ipsec.1"

  tags = {
    Name = "tc-vpc-cgw"
  }
}

resource "aws_vpn_gateway" "tc-vpc-vgw" {
  depends_on      = [module.vpc]
  amazon_side_asn = 64512
  vpc_id          = module.vpc.vpc_id

  tags = {
    Name = "tc-vpc-vgw"
  }
}

resource "aws_vpn_gateway_attachment" "tc-vpn-attach" {
  depends_on     = [module.vpc, aws_vpn_gateway.tc-vpc-vgw]
  vpc_id         = module.vpc.vpc_id
  vpn_gateway_id = aws_vpn_gateway.tc-vpc-vgw.id
}

resource "aws_vpn_gateway_route_propagation" "tc-vpn-propagate-to-pub" {
  vpn_gateway_id = aws_vpn_gateway.tc-vpc-vgw.id
  route_table_id = module.vpc.public_route_table_ids[0]
}

resource "aws_vpn_gateway_route_propagation" "tc-vpn-propagate-to-priv" {
  vpn_gateway_id = aws_vpn_gateway.tc-vpc-vgw.id
  route_table_id = module.vpc.private_route_table_ids[0]
}

위 코드 중 aws_vpn_gateway_attachment 리소스는 VPC와 VPN Gateway를 연결하는 설정으로 연결되지 않으면 문제가 발생합니다. 관련된 부분은 AWS 라우팅 테이블 미등록 문제에서 더 자세히 설명하겠습니다.

AWS의 VGW와 온프레미스 측의 CGW를 생성합니다. 그리고 퍼블릭 및 프라이빗 라우팅 테이블에 자동으로 전파하는 옵션을 설정합니다. 이 옵션이 설정되어 있지 않다면, AWS VPC의 라우팅 테이블에 온프레미스 네트워크 정보가 자동으로 갱신되지 않습니다.


2-2. AWS EKS Addons


Terraform으로 LoadBalancer Controller와 External-DNS를 정의하는 코드입니다.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# -----------------------------------------------------------------
# Addon - AWS Load Balancer Controller
# -----------------------------------------------------------------
# https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html
module "alb_controller_irsa" {
  depends_on = [module.eks]
  source     = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"

  role_name                              = "alb_controller_role"
  attach_load_balancer_controller_policy = true

  oidc_providers = {
    main = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

resource "kubernetes_service_account" "alb_controller" {
  metadata {
    name      = "aws-load-balancer-controller"
    namespace = "kube-system"

    annotations = {
      "eks.amazonaws.com/role-arn" = module.alb_controller_irsa.iam_role_arn
    }
  }
}

resource "helm_release" "aws-load-balancer-controller" {
  depends_on = [kubernetes_service_account.alb_controller]
  name       = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  namespace  = "kube-system"
  version    = ">= 1.8.0"

  values = [
    templatefile("${path.module}/alb_controller.yaml", {
      clusterName = module.eks.cluster_name,
      region      = var.region,
      vpcId       = module.vpc.vpc_id
      }
    )
  ]
}

loadbalancer_controller

로드밸런서 컨트롤러를 RBAC(Role-Based Access Controll)으로 최소 권한 원칙을 지키며 생성합니다.

이 때, Helm 차트를 통해 EKS 노드 위에 로드밸런서 컨트롤러 파드가 배포되고 쿠버네티스 서비스 계정으로 로드밸런서를 생성할 수 있게 됩니다.


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
module "external-dns-irsa-role" {
  depends_on = [module.eks]
  source     = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"

  role_name                     = "external-dns-irsa-role"
  attach_external_dns_policy    = true
  force_detach_policies         = false
  external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/*"]

  oidc_providers = {
    main = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:external-dns"]
    }
  }
}

resource "helm_release" "external-dns-helm" {
  depends_on = [module.external-dns-irsa-role]
  name       = "external-dns"
  repository = "https://kubernetes-sigs.github.io/external-dns/"
  chart      = "external-dns"
  namespace  = "kube-system"
  version    = ">= 1.14.5"

  values = [
    templatefile("${path.module}/external_dns.yaml", {
      serviceAccount = module.external-dns-irsa-role.iam_role_arn
      }
    )
  ]
}

external_dns

비슷한 방식으로 AWS Route 53의 호스팅 영역에 레코드를 작성할 수 있는 External-DNS 파드를 Helm 차트를 통해 배포하고 필요한 권한을 위해 RBAC을 이용하였습니다.


쿠버네티스 클러스터의 정보를 수집하여 kube-api로 전달하는 Metric Server와 EBS CSI Driver는 좀 더 간단한 형식으로 선언됩니다.

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
33
# -----------------------------------------------------------------
# Addon - EBS CSI Driver
# -----------------------------------------------------------------
data "aws_iam_policy" "ebs_csi" {
  name = "AmazonEBSCSIDriverPolicy"
}

resource "kubernetes_storage_class" "ebs-gp3-sc" {
  depends_on = [module.eks]
  metadata {
    name = "ebs-gp3-sc"
  }
  storage_provisioner = "ebs.csi.aws.com"
  reclaim_policy      = "Delete"
  volume_binding_mode = "WaitForFirstConsumer"
  parameters = {
    "csi.storage.k8s.io/fstype" = "ext4"
    type                        = "gp3"
  }
}

# -----------------------------------------------------------------
# Addon - Metric Server
# -----------------------------------------------------------------
# https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/metrics-server.html
resource "helm_release" "metrics-server" {
  depends_on = [module.eks]
  name       = "metrics-server"
  repository = "https://kubernetes-sigs.github.io/metrics-server/"
  chart      = "metrics-server"
  namespace  = "kube-system"
  version    = ">= 3.12.0"
}

다음은 terraform apply 명령어로 실제 리소스를 생성하는 영상이며, 생성 시간이 15분 이상 걸리는 관계로 배속으로 진행하였습니다.

outputs.tf 파일을 정의해 terraform apply를 통해 생성한 AWS 리소스에 대한 정보 중 IPSec 및 라우팅에 필요한 정보를 tfstate 파일에 저장합니다.

이후 진행할 IPSec 구성 및 자동화, FRR(Free Range Routing) 구성 및 자동화에서 tfstate 파일은 해당 Ansible 구성 정보 자동화의 입력으로 전환됩니다.


3. VPN 연결


3-1. IPSec 구성 및 자동화


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
---
- name: Configure settings for IPSec
  hosts:
    - VPN_Server
  vars_files:
    - "host_vars/vpn.yaml"
  vars:
    home: /home/user/automation/ansible/
  tasks:
    - name: Install libreswan for IPSec and jmespath for json_query
      dnf:
        name: libreswan, python3-jmespath
        state: present

    # Setting the "rp_filter" variable in "sysctl",
    # which can be changed to not block packets that are not registered in the Linux route table.

    # Copy the corresponding configuration file template to the "sysctl" configuration file directory location.
    
    - name: Copy forward.conf file to the sysctl.d
      template:
        src: ""
        dest: ""
        owner: root
        group: root
        mode: '0600'

    - name: Apply the "sysctl" configuration file
      command: "sysctl -p "

    # from .tfstate to register
    - name: Read .tfstate to register
      slurp:
        src: ""
      register: tfstate
      no_log: yes

    - name: Setting variables for AWS Site-to-Site VPN
      set_fact:
        tunnel1_address: ""
        tunnel1_cgw_inside_address: ""
        tunnel1_preshared_key: ""
        # tunnel1_inside_cidr: ""
        # tunnel1_vgw_inside_address: ""
        # tunnel1_preshared_key: ""
        tunnel2_address: ""
        tunnel2_cgw_inside_address: ""
        tunnel2_preshared_key: ""
        # tunnel2_inside_cidr: ""
        # tunnel2_vgw_inside_address: ""
        # tunnel2_preshared_key: ""
      no_log: yes
    
    # AWS Site-to-Site VPN Tunnel1 & Tunnel2 Preshared Key file
    - name: Copy aws.secrets file to the ipsec.d
      template:
        src: ""
        dest: ""

    # AWS Site-to-Site VPN Tunnel1 & Tunnel2 Configuration
    - name: Copy aws.conf file to the ipsec.d
      template:
        src: ""
        dest: ""

    - name: Gather ansible_facts services
      service_facts:
      no_log: yes

    # Check Firewall Service is available
    # If the status of firewalld is on, configure the firewall's settings.
    # ipsec service include 500/udp, 4500/udp, 4500/tcp ports and esp, ah protocols (50, 51)
    - name: IPSec Firewall port open
      firewalld:
        service: ipsec 
        state: enabled
        permanent: yes
        immediate: yes
      when: ansible_facts.services['firewalld.service'].state == 'running'

    - name: Enable ipsec.service
      service:
        name: ipsec
        state: restarted
        enabled: yes
      ignore_errors: yes

ipsec.yaml


리눅스 커널에서 IPSec에 필요한 설정을 적용하기 위해 sysctl -p [설정 파일 위치] 명령어를 수행합니다.

그리고 tfstate 파일을 읽어 JSON 쿼리를 통해 해당 정보를 변수에 담아 템플릿으로 설정 파일이 존재해야 하는 위치로 복사하게 됩니다.

1
2
3
4
5
6
7
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.*.rp_filter = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

ipsec-sysctl.conf

net.ipv4.ip_forward 설정은 IP 포워딩을 사용했을 때 패킷이 한 네트워크 인터페이스에서 다른 인터페이스(VTI로 전달)로 전달될 때 사용하는 설정입니다.

위의 설정들은 VPN 연결에 있어 정상적인 트래픽이어도 차단할 가능성이 존재하기 때문에 VPN 트래픽 흐름을 안정적으로 흐르게 하기 위해 활성화하거나 비활성화합니다.


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
conn aws-tunnel1
    authby=secret
    auto=start
    left=%defaultroute
    leftid=
    right=
    type=tunnel
    ikelifetime=8h
    keylife=1h
    ike=aes256-sha2_256;dh14
    phase2alg=aes_gcm256;modp1536
    ikev2=insist
    keyingtries=%forever
    keyexchange=ike
    leftsubnet=0.0.0.0/0
    rightsubnet=0.0.0.0/0
    dpddelay=10
    dpdtimeout=30
    dpdaction=restart_by_peer
    aggrmode=no
    rekey=yes
    encapsulation=yes
    mark=5/0xffffffff
    vti-interface=vti1
    vti-routing=no
    leftvti=/30

...

aws.conf

같은 방식으로 tunnel2에 대한 정의도 진행되지만 유사하기 때문에 생략하였습니다.

위 설정 파일은 PSK(Pre-Shared Key)의 유효 시간, 암호화 방식 등을 정의하는 설정 파일입니다.

AWS에서 제공하는 터널을 위한 외부 공인 IP와 온프레미스 공인 IP에서 위의 정보들로 안전한 터널을 생성하게 됩니다.


다음은 Ansible로 IPSec 구성 자동화를 진행하는 영상입니다.

모든 구성이 완료된 후 ipsec verify 명령어를 입력했을 때, 모든 결과가 반드시 OK로 출력되어야 합니다.


3-2. FRR 구성 및 자동화


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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
---
- name: Configure settings for Free Range Routing server
  hosts:
    - VPN_Server
  vars_files:
    - "host_vars/vpn.yaml"
  vars:
    home: /home/user/automation/ansible/
    FRRVER: frr-9
  tasks:

    - name: Install Free Range Routing(FRR) repo rpm
      dnf:
        name: ""
        state: present
        disable_gpg_check: true
    
    - name: Copy ospf_sysctl.conf file to the sysctl.d
      template:
        src: ""
        dest: ""
        owner: root
        group: root
        mode: '0600'

    - name: Apply the "sysctl" configuration file for ospf dynamic routing
      command: "sysctl -p "

    - name: Install frr
      dnf:
        name: frr
        state: present

    - name: Enable bgpd and ospfd daemons
      shell: ""
      loop:
        - "sudo sed -i 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons"
        - "sudo sed -i 's/ospfd=no/ospfd=yes/g' /etc/frr/daemons"
      loop_control:
        loop_var: daemons

    - name: Copy vtysh.conf file to the /etc/frr
      template:
        src: ""
        dest: ""
        owner: frr
        group: frr
        mode: '0600'
    
    - name: Read .tfstate to register
      slurp:
        src: ""
      register: tfstate
      no_log: yes
    
    - name: Setting variables for AWS Site-to-Site VPN
      set_fact:
        tunnel1_inside_cidr: ""
        tunnel1_vgw_inside_address: ""
        tunnel1_cgw_inside_address: ""
        # tunnel1_address: ""
        # tunnel1_preshared_key: ""
        tunnel2_inside_cidr: ""
        tunnel2_vgw_inside_address: ""
        tunnel2_cgw_inside_address: ""
        # tunnel2_address: ""
        # tunnel2_preshared_key: ""
      no_log: yes

    - name: Copy vtysh command sample file to home
      template:
        src: ""
        dest: ""
    
    # - name: Configure global bgp as 65000 for aws
    #   frr.frr.frr_bgp:
    #     config:
    #       bgp_as: 65000
    #       router_id: ""
    #       log_neighbor_changes: true
    #       neighbors:
    #       - neighbor: ""
    #         remote_as: 64512
    #         ebgp_multihop: 255
    #         enabled: true
    #       - neighbor: ""
    #         remote_as: 64512
    #         ebgp_multihop: 255
    #         enabled: true
    #       address_family:
    #       - afi: ipv4
    #         safi: unicast
    #         neighbors:
    #         - neighbor: ""
    #           activate: true
    #         - neighbor: ""
    #           activate: true
    #         - neighbor: ""
    #           activate: true
    #           route_reflector_client: true
    #         redistribute:
    #         - protocol: ospf
    #           id: 0

    - name: Gather ansible_facts services
      service_facts:
      no_log: yes

    # Check Firewall Service is available
    # If the status of firewalld is on, configure the firewall's settings.
    - name: FRR VPN BGP service open (179/tcp)
      firewalld:
        service: bgp
        state: enabled
        permanent: yes
        immediate: yes
      when: ansible_facts.services['firewalld.service'].state == 'running'

    - name: Firewall protocol icmp open for test network 
      firewalld:
        protocol: icmp
        state: enabled
        permanent: yes
        immediate: yes
      when: ansible_facts.services['firewalld.service'].state == 'running'

    - name: Firewall add interface vti1, vti2 at public zone
      firewalld:
        zone: public
        permanent: yes
        immediate: yes
        state: enabled
        interface: ""
      loop:
        - vti1
        - vti2
      loop_control:
        loop_var: tunnel_interface

    - name: Enable frr.service and firewalld.service
      service:
        name: ""
        state: restarted
        enabled: yes
      loop:
        - frr
        - firewalld
      loop_control:
        loop_var: frr_services
      ignore_errors: yes

VPN 서버의 방화벽의 Public 영역에 기존에 존재하던 인터페이스 이외에 vti(Virtual Tunnel Interface)를 추가해줌으로써 방화벽의 설정을 터널 인터페이스에도 적용할 수 있습니다.

IPSec 구성에서 커널 설정이 필요했던 것처럼 라우팅에도 필요한 리눅스 커널 설정이 존재하기 때문에 유사한 방식으로 진행해 적용합니다.


다음은 Ansible로 FRR 구성을 자동화하며 tfstate 파일을 읽은 정보를 토대로 명령어 입력 순서대로 정리한 파일을 출력하며, 관련 설정을 진행하는 영상입니다.


이제 Ansible 플레이북의 출력으로 생성된 파일로 라우터 명령어를 입력하는 단계입니다. 라우터 명령어 입력까지도 Ansible로 자동화하는 것은 계속해서 발전 중에 있습니다.

다음은 VPN 연결을 위해 입력해야할 라우터 명령어를 정리한 것입니다.

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
33
34
35
36
37
38
39
40
41
sudo vtysh

configure terminal

router bgp 65000

  bgp router-id 
  
  bgp log-neighbor-changes
  
  no bgp ebgp-requires-policy
  
  bgp graceful-restart
  
  neighbor  remote-as 64512
  neighbor  ebgp-multihop 255
  neighbor  remote-as 64512
  neighbor  ebgp-multihop 255
  
  address-family ipv4 unicast
  
    redistribute ospf
    
    network 
    network 
    network 
    
    exit

  exit

router ospf

  network  area 0
  network  area 0

  exit

exit

write file

위 라우터 명령어를 입력한 결과는 다음과 같습니다.


4. 테스트

IPSec 구성이 완료되고, vtysh를 통해 라우터 명령어까지 입력했다면 AWS 콘솔을 통해 연결이 잘 이루어졌는지 확인할 수 있습니다.

AWS VPN 연결 성공 AWS VPN 연결 성공


이후 VPN 연결을 통해 온프레미스 네트워크(192.168.2.0/24) 대역에서 AWS CIDR(172.20.0.0/16)로 통신이 가능한지 테스트합니다.


CI/CD를 담당한 팀원이 Jenkins와 Argo CD 웹 관리 페이지에 VPN을 통해 접속할 수 있게 되었습니다. 이 때, EKS 쿠버네티스 노드 포트를 통해 접속하며 Argo CD는 30101번 포트, Jenkins는 32000번 포트로 임의 지정하였습니다.

CI/CD 관리 웹 페이지 CI/CD 관리 웹 페이지



5. Trouble Shooting


5-1. 서버와 시스템 시간 불일치 문제

시간 불일치 확인

위 사진은 VS Code에서 SSH를 통해 VPN 서버에 접속한 모습인데, 리눅스의 시스템 시간과 호스트 OS의 시스템 시간이 일치하지 않아 SignatureDoesNotMatch 에러가 발생하고 있습니다.

aws sts get-caller-identity 명령어는 AWS에서 작업을 호출하는 데 사용되는 자격 증명을 가진 IAM 사용자 또는 역할에 대한 세부 정보를 검색하는 데 사용됩니다.

해당 정보에 접근하기 위해서 aws configure 명령으로 특정 계정에 대한 액세스 키와 비밀 액세스 키를 등록합니다.

위 에러는 해당 키를 이용해 접근한 사용자의 정보를 가져오는 도중, AWS 서버의 시간과 해당 요청을 보낸 시스템의 시간이 달라서 발생하는 에러입니다.


따라서 시간을 일치시키기 위해 chronyd 또는 ntpd 등의 도구를 설치해야 합니다.

저는 이 문제를 해결하기 위해 chronyd를 설치하기로 결정했고 그 이유는 chronydntpd에 비해 정확성과 속도가 빠르며, chronydntpd와 달리 여러 시간 서버에 동시 요청을 지원하기 때문에 일관성 유지에 도움이 됩니다.

마지막으로 chronydntpd에 비해 시스템 자원을 더 적게 사용하기 때문입니다.

다음은 입력한 명령어의 목록입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo dnf install chrony -y

sed -i 's/^pool/#pool/g' /etc/chrony.conf
sed -i 's/^server/#server/g' /etc/chrony.conf

cat <<EOF | sudo tee /etc/chrony.conf
server time.bora.net
server time.google.com
EOF

sudo systemctl enable --now chronyd

chronyc sources -v

chronyc tracking

chrony를 설치하고, 동기화하려는 서버를 지정합니다. 이후 서비스를 시작과 동시에 재부팅 시에도 자동으로 chronyd 서비스가 시작할 수 있도록 설정합니다.

시간 서버 데몬을 실행시켜도 시간이 동기화되지 않을 수 있기 때문에, chronyc tracking 명령어를 입력해야 합니다.


5-2. EKS 클러스터 생성 권한 미지정 문제

권한 획득 실패 권한 획득 실패

권한을 얻는 과정에서 실패하는 것을 확인할 수 있습니다.

이는, Terraform 모듈 중 EKS를 설정할 때 여러 테스트를 진행하다가 필수 설정을 제외하면서 발생한 오류입니다.


클러스터 생성 권한 필수 설정 클러스터 생성 권한 필수 설정

enable_cluster_creator_admin_permissions = true boolean 값을 true로 설정하면, eks 모듈을 통해 eks 클러스터를 생성하는 사용자에게도 AmazoneEKSClusterAdminPolicy 를 부여합니다.

아래의 access_entries EKS 클러스터를 생성하며 함께 작업할 동료에게 권한을 부여할 때 지정하는 필드를 의미합니다.


5-3. AWS 라우팅 테이블 미등록 문제

VPN Gateway 미연결 VPN Gateway 미연결

위 사진에서 VPN 게이트웨이가 NotAttached 되어 있다고 에러 메시지가 나오는데, 이는 Terraform에서 VPC 리소스와 VPN 리소스를 정의하는 부분에서 해결할 수 있습니다.


VPN 필수 설정 VPN 필수 설정

위 사진은 Terraform에서 VPC를 정의할 때 VPN 연결을 위해 필수적인 설정입니다.


Attachment Attachment

이 문제가 발생한 근본적인 원인으로, VPN 게이트웨이가 연결되지 않았었는데, 위 사진처럼 리소스를 정의하면 해결할 수 있습니다.

module.vpc.private_route_table_ids 항목은 리스트 형태로 전달되기 때문에, 인덱싱을 통해 첫 번째 요소를 지정함으로써 퍼블릭 서브넷에 할당되는 라우팅 테이블과, 프라이빗 서브넷에 할당되는 라우팅 테이블에 온프레미스 네트워크 정보를 자동으로 전파할 수 있게 됩니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.