티스토리 뷰

원문 : RUNNING ONLINE SERVICES AT RIOT: PART II

이 글을 포스팅한 Jonathan McCaffrey는 Riot의 인프라 팀에서 일하고 있습니다.

이 시리즈의 첫 번째 게시물에서는 글로벌 백엔드 기능을 배포하고 운영하는 방법에 대해 자세히 설명할 것이다. 기술 세부 사항을 살펴보기 전에, Rioters가 생각하는 기능 개발이 어떤 것인지 이해하는 것이 중요하다. Riot에서 가장 중요한 가치는 플레이어이므로, 개발 팀은 종종 플레이어 커뮤니티와 직접 협력하여 기능 및 개선 사항을 전달한다. 최상의 플레이어 경험을 제공하기 위해 우리는 신속하게 움직이고, 피드백을 기반으로 계획을 신속하게 변경할 수 있는 능력을 유지해야한다. 인프라 팀의 임무는 개발자가 바로 그렇게 할 수있는 길을 열어 놓는 것이다. 즉, 우리가 Riot 팀에 더 많은 권한을 부여할수록, 더 빠르게 기능들을 플레이어들에게 제공하여 즐길 수 있게 된다.

물론 이는 말로 하긴 쉽지만 배포의 다양한 특성을 고려할 때 많은 문제가 발생한다. (공공 클라우드, 개인 데이터 센터, Tencent 및 Garena와 같은 파트너 환경의 서버 등 지리적으로나 기술적으로 다양하다.) 이러한 복잡성으로 인해 피쳐 팀은 출시 준비가되었을 때 막대한 부담을 안게된다. 바로 이 부분에서 인프라 팀을 필요로 하게 된다. 우리는 최근 'rCluster'라고 부르는 컨테이너 기반 내부 클라우드 환경을 통해 이러한 배포 장애물 중 일부를 제거하는 데 진전을 이루었다. 이 게시물에서는 Riot의 수동 배치에서부터 현재 rCluster를 사용하여 전세계에 런칭을 한 여정에 대해 다룬다. rCluster의 오퍼링과 기술을 보여주기 위해 Hextech Crafting 시스템에 대해 알아보도록 하겠다.

역사의 한조각 (A BIT OF HISTORY)

7년 전 Riot이 시작하였을 때 배포 또는 서버 관리 프로세스가 거의 없었다. 우리는 커다란 아이디어와 작은 예산 그리고 빠른 이동의 필요성을 가진 신생 기업이었고, 리그 오브 레전드의 프로덕션 인프라를 구축하면서 게임을 위한 요구 사항, 개발자들로부터의 더 많은 기능 지원에 대한 요구 사항 및 전 세계의 새로운 지역으로 출시하기 위한 각 지역팀으로부터의 요구사항을 위해 지속적으로 노력했습니다. 가이드 라인이나 전략적인 계획을 거의 고려하지 않고 수동으로 서버와 응용 프로그램을 구축했었다.

그 과정에서 우리는 Chef를 활용하여 많은 공통 배포 및 인프라 작업들을 수행하였고, big data와 web effort를 위해 더 많은 공개 클라우드를 사용하기 시작했다. 이러한 발전으로 인해 네트워크 설계, 공급 업체 선택 및 팀 구조가 여러 번 변경이 되었었다.

우리의 데이터 센터는 수천 대의 서버를 수용했으며, 대부분의 새로운 애플리케이션을 위해 새로운 서버가 설치되었었다. 자체적으로 제작한 VLAN(네트워크 간의 보안 액세스를 가능하게 하기 위해 손수 만든 라우팅 및 방화벽 규칙을 사용하여 제작했었음.) 위에 새로운 서버들이 존재했다. 이 프로세스가 보안 및 명백하게 잘못 정의된 도메인에 도움을 주었었지만 힘들고 시간이 많이 걸렸었다. 이 디자인의 어려움을 해소하기 위해 당시의 새로운 기능 대부분은 작은 웹 서비스로 설계되어서 LoL 생태계에서 고유 한 응용 프로그램의 수가 급격히 증가했다.

이에 더해 개발 팀은 특히 구성 및 네트워크 연결과 같은 배포 시 이슈가 발생했을 때 그들의 응용 프로그램을 테스트하는 것에 자신이 없었다. 앱을 물리적 인프라에 너무 밀접하게 결속시키면 QA, 스테이징 및 PBE에서 프로덕션 데이터 센터 환경 간의 차이가 복제되지 않았다. 각각의 환경은 손으로 직접 만들어 고유해져서, 결국에는 일관성이 없었다.

끊임없이 증가하는 응용 프로그램이있는 생태계에서 이러한 수동 서버 및 네트워크 프로비저닝의 문제를 해결하는 동안, Docker는 구성 일관성 및 개발 환경 문제로 인한 문제를 해결하기위한 수단으로 우리의 개발 팀 내에서 인기를 얻기 시작했다. 일단 우리가 작업을 시작하자 Docker로 더 많은 것을 할 수 있었으며 인프라에 접근하는 방법에 대한 결정적인 역할을 할 수 있었다.

2016년, 그리고 그 이후

인프라 팀은 2016년도에 플레이어, 개발자 그리고 Riot을 위해 이 문제들을 해결하기 위한 목표를 세웠다. 2015년 말까지 우리는 수동으로 기능을 배포하는 대신 자동화되고 일관된 방식으로 Riot 지사들에서 Hextech Crafting으로 기능을 배포했다. 우리의 해결책은 Docker와 마이크로 서비스 아키텍처의 Software Defined Networking을 활용 한 새로운 시스템 인 rCluster였다. rCluster로 전환하면 환경 및 배포 프로세스의 불일치를 해결하고 제품팀이 제품에 집중할 수 있다.

이 기술을 통해 rCluster가 Hextech Crafting과 같은 기능을 어떻게 지원하는지 살펴보도록 하자. 상황에 따라 Hextech Crafting은 플레이어들에게 게임 내 컨텐츠를 잠금 해제하기 위한 새로운 방법을 제공하는 League of Legends의 기능이다.

이 기능은 내부적으로 "Loot"로 알려져 있으며 다음과 같은 3 가지 핵심 구성 요소로 이루어져 있다.

  • Loot Service - HTTP / JSON ReST API를 통해 Loot 요청을 처리하는 Java 응용 프로그램.
  • Loot Cache - 모니터링, 설정(configuration) 및 시작 / 중지 작업을 위해 Memcached와 작은 golang 사이드카를 사용하는 캐싱 클러스터.
  • Loot DB - 마스터 및 다중 슬레이브가있는 MySQL DB 클러스터.

제작 화면을 열면 다음과 같다.

  1. 플레이어가 클라이언트에서 제작 화면을 연다.
  2. 클라이언트는 플레이어와 내부 백엔드 서비스간에 호출을 프록시하는 "feapp"라고하는 프론트 엔드 애플리케이션에 RPC 호출을 한다.
  3. Loot 서버에 대한 feapp 호출
    1. feapp는 IP와 Port 정보를 찾기 위해 "Service Discovery"에서 Loot Service를 찾는다.
    2. feapp는 Loot Service를 위해 HTTP GET 호출을 한다.
    3. Loot Service는 Loot Cache를 확인하여 플레이어의 인벤토리가 있는지 확인한다.
    4. 인벤토리가 캐시에 없으므로 Loot Service는 Loot DB를 호출하여 플레이어가 현재 소유하고 있는 것을 확인하고 캐시에 결과를 채 웁니다.
    5. Loot Service는 GET 호출에 응답한다.
  4. feapp는 RPC 응답을 다시 클라이언트에 전송한다.

Loot 팀과 협력하여 Docker 컨테이너에 내장 된 서버 및 캐시 계층과 JSON 파일에 정의 된 배포 구성을 다음과 같이 만들 수 있었다.

  • Loot Server의 JSON 예

    {
        "name": "euw1.loot.lootserver",
        "service": {
            "appname": "loot.lootserver",
            "location": "lolriot.ams1.euw1_loot"
        },
        "containers": [
            {
                "image": "compet/lootserver",
                "version": "0.1.10-20160511-1746",
                "ports": []
            }
        ],
        "env": [
            "LOOT_SERVER_OPTIONS=-Dloot.regions=EUW1",
            "LOG_FORWARDING=true"
        ],
        "count": 12,
        "cpu": 4,
        "memory": 6144
    }
  • Loot Cache의 Json 예

    {
        "name": "euw1.loot.memcached",
        "service": {
            "appname": "loot.memcached",
            "location": "lolriot.ams1.euw1_loot"
        },
        "containers": [
            {
                "name": "loot.memcached_sidecar",
                "image": "rcluster/memcached-sidecar",
                "version": "0.0.65",
                "ports": [],
                "env": [
                    "LOG_FORWARDING=true",
                    "RC_GROUP=loot",
                    "RC_APP=memcached"
                ]
            },
            {
                "name": "loot.memcached",
                "image": "rcluster/memcached",
                "version": "0.0.65",
                "ports": [],
                "env": ["LOG_FORWARDING=true"]
            }
        ],
        "count": 12,
        "cpu": 1,
        "memory": 2048
    }

그러나 이 기능(앞에서 설명했던 문제를 완화 시키기 위한 과정들)을 실제로 배포하기 위해 북미, 남미, 유럽, 아시아와 같은 전 세계에 Docker를 지원할 수 있는 클러스터를 만들어야 했다. 이러한 어려운 문제를 해결하기 위해 우리는 아래와 같은 것들이 필요했다.

  • 스케줄링 컨테이너 (Scheduling Containers)
  • Docker와의 네트워킹 (Networking With Docker)
  • 지속적인 배포 (Continuous Delivery)
  • 동적 응용 프로그램 실행 (Running Dynamic Applications)

차후의 게시물들에서 rCluster 시스템의 이러한 구성 요소에 대해 자세히 설명할 것이므로 여기에서는 간단하게 설명하도록 하겠다.

스케쥴링

우리는 Admiral이라는 소프트웨어를 사용하여 rCluster 에코 시스템의 컨테이너 스케줄링을 구현했다. Admiral은 현재 라이브 상태를 파악하기 위한 다수의 물리적인 시스템을 가로지르는 Docker 데몬과 통신한다. 사용자는 HTTPS를 통해 위에서 언급한 JSON을 전송하는 것으로 요청을 하게 된다. (Admiral은 관련 컨테이너들을 원하는 상태로 업데이트하기 위해 이를 사용한다). 그런 다음 클러스터의 라이브 및 원하는 상태를 지속적으로 스윕하여 필요한 조치가 무엇인지 파악한다. 마지막으로 Admiral은 컨테이너를 시작 및 중지하여 원하는 상태가 적용되도록 추가적으로 Docker 데몬을 호출합니다.

컨테이너가 크래쉬나면 Admiral은 라이브와 원하는 상태간의 차이를 보게되고 다른 호스트에서 컨테이너를 시작하여 이를 수정한다. 이러한 유연성 덕분에 서버를 원활히 흐르게하고 유지 보수를 수행 한 다음 워크로드를 다시 사용할 수 있기 때문에 서버 관리가 훨씬 쉬워졌다.

Admiral은 오픈 소스 도구 인 Marathon과 유사하므로 현재 Mesos, Marathon 및 DC / OS를 활용하여 작업을 포팅하는 방법을 연구 중이다. 그 일이 성과가 있다면, 앞으로의 게시글에서 그것에 대해 소개하도록 하겠다.

Docker와의 네트워킹

컨테이너가 실행되면 Loot 애플리케이션과 에코시스템의 다른 부분 사이에 네트워크 연결을 제공해야한다. 이를 위해 OpenContrail을 활용하여 각 애플리케이션에 사설 네트워크를 제공하고 GitHub에 있는 JSON 파일을 사용하여 자체 개발 팀이 자체적으로 정책을 관리 할 수있게 하였다.

  • Loot Server Network

    {
        "inbound": [
            {
                "source": "loot.loadbalancer:lolriot.ams1.euw1_loot",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "riot.offices:globalriot.earth.alloffices",
                "ports": [
                    "main",
                    "jmx",
                    "jmx_rmi",
                    "bproxy"
                ]
            },
            {
                "source": "hmp.metricsd:globalriot.ams1.ams1",
                "ports": [
                    "main",
                    "logasaurous"
                ]
            },
            {
                "source": "platform.gsm:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "platform.feapp:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "platform.beapp:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "store.purchase:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "pss.psstool:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "championmastery.server:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            },
            {
                "source": "rama.server:lolriot.ams1.euw1",
                "ports": [
                    "main"
                ]
            }
        ],
        "ports": {
            "bproxy": [
                "1301"
            ],
            "jmx": [
                "23050"
            ],
            "jmx_rmi": [
                "23051"
            ],
            "logasaurous": [
                "1065"
            ],
            "main": [
                "22050"
            ]
        }
    }
  • Loot Cache Network:

    {
        "inbound": [
            {
                "source": "loot.lootserver:lolriot.ams1.euw1_loot",
                "ports": [
                    "memcached"
                ]
            },
            {
                "source": "riot.offices:globalriot.earth.alloffices",
                "ports": [
                    "sidecar",
                    "memcached",
                    "bproxy"
                ]
            },
            {
                "source": "hmp.metricsd:globalriot.ams1.ams1",
                "ports": [
                    "sidecar"
                ]
            },
            {
                "source": "riot.las1build:globalriot.las1.buildfarm",
                "ports": [
                    "sidecar"
                ]
            }
        ],
        "ports": {
            "sidecar": 8080,
            "memcached": 11211,
            "bproxy": 1301
        }
    }

엔지니어가 GitHub에서 이 구성을 변경하면 변환기의 작업이 실행되고 Contrail에서 API 호출을 수행하여 애플리케이션의 사설 네트워크에 대한 정책을 만들고 업데이트한다.

Contrail은 Overlay Networking이라는 기술을 사용하여 이러한 사설 네트워크를 구현한다. 우리의 경우, Contrail은 GRE 터널을 사용하여 compute 호스트와 게이트웨이 라우터 사이에서 오버레이 터널로 오고가는 트래픽을 관리하고 네트워크의 나머지 부분으로 전달한다. OpenContrail 시스템은 표준 MPLS L3VPN에서 영감을 얻었으며 개념적으로 매우 유사하다. 아키텍처의 더 자세한 내용은 여기에서 찾을 수 있다.

이 시스템을 구현하면서 몇 가지 주요 문제를 해결해야했다.

  • Contrail과 Docker 간의 통합
  • rCluster 외부의 나머지 네트워크가 새로운 오버레이 네트워크에 원활하게 액세스 할 수 있도록 허용
  • 한 클러스터의 응용 프로그램이 다른 클러스터와 통신하도록 허용
  • AWS 상단에서 오버레이 네트워크 실행
  • 오버레이에서 HA edge-facing 응용 프로그램 구축

지속적인 배포

Max Stewart는 이전에 Riot이 Docker를 지속적으로 사용하는 것에 대해 포스팅했었는데, rCluster에서 이를 활용한다.

Loot 애플리케이션의 경우 CI 플로우는 다음과 같다.

일반적인 목표는 마스터 저장소가 변경되면 새로운 애플리케이션 컨테이너가 만들어져 QA 환경에 배포된다는 것이다. 이 워크 플로를 통해 팀은 코드를 신속하게 반복하여 테스트할 수 있고, 작업중인 게임에 반영된 변경 사항을 확인할 수 있었다. 이러한 빈틈 없는 피드백 루프는 경험을 신속하게 개선 할 수있게 한다. 이는 Riot에서 플레이어 중심 엔지니어링의 핵심 목표이다.

실행중인 동적 응용 프로그램

이 글에서는 Hextech Crafting과 같은 기능을 구축하고 배포하는 방법에 대해 살펴 보았다. 그러나 이와 같은 컨테이너 환경에서 작업하는 데 많은 시간을 할애했다면 그것이 전체적인 문제가 아니라는 것을 알 것이다.

rCluster 모델에서는 컨테이너는 동적 IP 주소를 가지며 계속해서 위아래로 회전한다. 이것은 이전의 정적 서버 및 배포 방식과 완전히 다른 패러다임이므로 이와같은 새로운 도구와 절차가 효과적이다.

제기 된 주요 쟁점은 다음과 같다.:

  • 애플리케이션의 용량과 엔드 포인트가 항상 변경되는 경우 어떻게 모니터링합니까?
  • 한 앱이 항상 변화하는 경우 다른 앱의 end point를 어떻게 알 수 있습니까?
  • 새 컨테이너가 시작될 때마다 컨테이너로 ssh 할 수 없으며 로그가 재설정되는 경우 한 가지 분류 어플리케이션이 어떻게 발생합니까?
  • 빌드 시 컨테이너를 생성하는 중이라면 내 DB의 패스워드나 북미 vs 터키를 위해 옵션을 토글 시키는 것과 같은 설정들은 어떻게 구성하나요?

이를 해결하기 위해 Service Discovery, Configuration Managerment 및 모니터링과 같은 것들을 처리하기위한 마이크로 서비스 플랫폼을 구축해야했다. 우리는 이 시리즈의 마지막 부분에서 이 시스템과 우리가 해결 해야할 문제에 대해 자세히 설명할 것이다.

결론

이 게시물을 통해 Riot이 플레이어 가치를 더 쉽게 제공 할 수 있도록 해결하려고 시도한 문제들에 대한 overview를 주었기를 바란다. 앞서 언급했듯이 우리는 rCluster의 스케쥴링 사용, Docker와의 네트워킹, 동적 응용 프로그램 실행에 초점을 맞춰서 순차적으로 게시글을 연재 할 것이다. 게시글이 작성 될때마다 여기에 링크를 업데이트하도록 할 것이다.

비슷한 여정을 하고 있거나 참여하고 싶다면 아래의 글을 주의깊게 봐주길 바란다.


'Work > 번역' 카테고리의 다른 글

[번역] Logstash 배포 및 확장  (0) 2017.03.14
(번역) Riot에서 구동중인 온라인 서비스 - 1. 소개  (0) 2017.01.30
댓글
댓글쓰기 폼