RIOT 에서 온라인 서비스 운영하기: PART I (한글 번역)

출처 : RIOT GAMES ENGINEERING
피드백은 자유롭게 부탁드립니다.
9월 22일, 2016 (원본)

RIOT 에서 온라인 서비스 운영하기: PART I

제 이름은 조나탄이며 저는 RIOT의 인프라 팀에서 일하고 있습니다. 이 글은 백엔드 기술들을 어떻게 deploy하고 operate 할지 심도있게 다룰 것이며 연결된 글도 작성할 예정입니다. 기술적으로 깊게 들어가기 이전에, Rioters 가 기능 개발에 대해 어떻게 생각하는 지 알아야 합니다. 플레이어의 가치는  무엇보다 중요하며, 개발 팀은 플레이어 커뮤니티와 직접 연결하여 기능들과 개선 점들을  제작 및 수정합니다. 최선의 플레이어 경험을 제공하기 위해, 우리는 빠르게 움직여야 했고, 피드백에 의한 계획 변경에 빠르게 대응해야 했습니다. 인프라 팀의 미션은 개발팀이 일을 바로 수행할 수 있도록 제공해 주는 것이며, 이는 개발 팀에게 힘을 싣어 줄수록, 플레이어에게 기능이 빨리 제공되기 때문입니다.

물론, 말하는 것 처럼 쉽게 되지는 않습니다! 다양한 개발 환경에 따라 많은 도전들이 필요합니다. 우리는 지리적으로, 기술적으로  다양한 – 클라우드, 데이터센터, Tencent와 Garena 같은 파트너 사에서 서버를 운용하고 있습니다. 위와 같은 복잡환 환경 덕에 기능이 출시 될 때 많은 부담이 있습니다. 그래서 인프라 팀이 이 때 역할을 합니다 – 우리는 개발 팀의 장애물을 우리가 “rCluster”라고 부르는 Container-based 클라우드 환경을 통해 제거 했습니다. 이 글에서는 과거 수동 Deploy에서 시작하여 현재까지 Riot의 여정을을 소개할 것이며, “rCluster”의 기술에 대해서도 소개할 것입니다. 이와 연관되어 Hextech Crafting 에 대해서도 소개 하겠습니다.

Riot의 역사에 대해서..

제가 7 년 전 Riot에서 일을 시작했을 때, 우리는 개발이나 서버 관리 프로세스가 많지 않았습니다. 우리는큰 아이디어를 가진, 예산이 적은 스타트업이었으며, 빠르게 움직였어야 했습니다. League of Legends의 Prod 인프라를 만들면서, 게임에 대한 요구, 개발자의 더 많은 기능 지원 요구 및 전 세계 새로운 지역에서의 요구 사항을 해결하기 위해 지속적으로 노력했습니다. 우리는 서버와 앱을 수동적으로 구성했으며, 전략 계획이나 가이드라인에 신경을 많이 쓰지 못했습니다.

그 과정에서, 우리는 셰프를 활용하여 많은 일반적인 배치 및 인프라 작업을 수행했습니다. 그리고 우리는 big data와 웹에 클라우드를 적극적으로 활용하였습니다. 위와 같은 혁명은 우리에게 새로운 네트워크 디자인, 벤더 선택, 팀 구성을 만들어 주었습니다.

우리의 데이터센터들은 수천 개의 서버를 수용했으며, 새로운 앱이 나올 때마다 서버를 추가했습니다. 새 서버들은 VLAN과 수동 Routing, 방화벽 설정을 통해 만들어 졌으며, 이 작업이 보안과 잘못된 도메인을 알아내는 데에 긍정적인 작용을 했지만 작업이 힘들고 시간이 굉장히 많이 필요했습니다. 이 디자인의 어려움을 해결하기 위해 작은 웹 서비스를 만들기 시작했고, 유니크한 앱이 LoL 환경에 굉장히 많이 생겼습니다.

이와중에, 우리 개발 팀이 만드는 앱에 대한 테스트를 할 자신감이 부족했고, 특히 네트워크 연결과 설정 부분에서 Deploy 시간 문제가  어려웠습니다. 앱이 물리적인 인프라에 묶여있었기에 다른 Prod 데이터센터와의 호환이 잘 되지 않았습니다. (QA, Staging, and PBE)  각 환경은 수동적으로 만들어 졌으며, 결국 일관성은 없어졌습니다.

우리가 위와 같은 어려움에 처해있을 때, Docker가 우리 개발 팀에게 구성 일관성 및 개발 환경문제 해결을 위한 수단으로 유명해 지기 시작했습니다. Docker를 사용하기 시작하면서, Docker을 더 많이 사용해야겠다는 것이 명확해졌고, 인프라 계획에 중요한 역할을 할 것이라는 확신이 있었습니다.

2016년 시즌과 미래

인프라팀이 플레이어, 개발자 와 Riot를 위해 2016년 시즌에 이 문제를 해결하겠다고 목표를 설정했습니다. 2015 말경, 우리는 수동 Deploy를 Hextech Crafting과 같은  자동화 기능으로 바꾸었습니다. 이후 우리에게 새로운 해결책이 등장하였고 rCluster로 바꾸었습니다. rCluster은 마이크로 서비스 아키텍처를  네트워크에 적용하고 Docker와 소프트웨어를 이용한 새로운 시스템이었습니다.

기술적인 이야기를 시작하겠습니다. Hextech 같은 rCluster가 어떤 역할을 하는지 보겠습니다.  Hextech Crafting 은 LoL 플레이에게  게임 속 챔피언 Unlocking 를 제공해주는 서비스입니다.

 위 기능은 “Loot” 로 알려져 있으며 3가지 핵심 요소로 구성되어 있습니다:

  • Loot Service – Java app 이 HTTP/JSON ReST API를 통해 Loot requests 받음.

  • Loot Cache – Memcached를 이용한 캐싱 및 모니터링, 설정, 시작/정지를 위한 작은 golang sidecar

  • Loot DB –  master 와 여러 multiple slaves를 가진 MySQL DB.

플레이어가 LoL 마법공학 제작대를 실행하면, 아래와 같이 진행됩니다:

  1. 플레이어가 Client 통해 LoL 마법공학 제작대를 실행.

  2. 클라이언트는 플레이어와 내부 백엔드 서비스간에 호출을 프록시하는 “feapp”라고하는 프론트 엔드 애플리케이션에 RPC 호출합니다.

  3. The feapp 이 Loot Server를 호출합니다.

    1. The feapp 가 “Service Discovery”에 있는  Loot Service IP 와 port 정보 획득.

    2. The feapp 가 HTTP GET call 시행.

    3. The Loot Service 가 Loot Cache 에 플레이어의 인벤토리 존재여부 파악.

    4. 인벤토리 캐시되어있지 않을 경우 LootDB에서 찾아본 후 캐시.

    5. Loot Service 가 GET call에 응답.

  4. The feapp 가 RPC 응답을 Client에게 전달.

Loot팀과 일하면서, 서버와 캐시 layer를 Docker Container에 설치할 수 있었으며, Json 파일로 설정이 되어 있습니다. Json파일은 아래와 같습니다.

Loot 서버 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 캐시 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
}

그럼에도 불구하고, 실제로 이 기능을 Deploy하기 위해서, 북미, 남미, 유럽, 아시아 등에 Docker를 지원하는 클러스터를 만들어야 했습니다. 이것을 해결하기 위해서 아래와 같은  어려운 문제점들을 만났습니다 :

  • Containers 스케줄링

  • Docker 네트워킹

  • Continuous Delivery

  • Dynamic 앱 운용

아래 포스팅에서 rCluster 시스템과 관련된 위 요소들에 대해 자세히 설명하겠습니다.

Containers 스케줄링

우리는 Admiral이라는 소프트웨어를 통해 rCluster 환경의 container 스케줄링을 진행했습니다. Admiral 은 Docker 데몬들과 통신하여 여러 물리적인 컴퓨터들에 있는 상태를 확인 할 수 있습니다. 유저가 위 Json을 HTTPS를 통해 요청을 하면, Admiral이 관련된 containers의 상태를 알 수 있습니다. 그리고 Admiral은 지속적으로 live와 원하는 상태 정보를 가져와 어떤 Action이 필요한지 알려줍니다. 마지막으로, Admiral은 Docker데몬에 추가적인 호출하여 시작/정지를 할 수 있습니다.

만약 container에 이상이 생기면, Admiral은 live 과 원하는 상태를 체크한 후, 다르다고 인지되면 새로운 container를 다른 호스트에 만들어 상태를 정정합니다. 이러한 유연성 덕분에 쉽게 작업할 수 있으며, “없애”버리고, 유지 관리 후 다시 작업을 시작할 수 있습니다.

Admiral 은  Marathon 오픈소스 툴과 유사합니다. 그래서 현재 Mesos, Marathon, and DC/OS에 대한 테스트를 진행중에 있습니다. 만일 잘 된다면, 미래의 포스팅에 글을 쓰겠습니다.

DOCKER 네트워킹

Containers가 잘 실행되면, Loot 앱 과 다른 환경 간의 네트워크 연결을 해야 합니다. 그러기 위해서, 우리는 OpenContrail 을 사용하여 각각의 앱에 사설 네트워크를 배정했으며, 개발 팀에게 정책을 관리하게 한 후 json을 Github에 올리게 했습니다.

Loot 서버 네트워크:

{
    "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 캐시 네트워크:

{
    "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에 있는 위 설정을 바꿀 때, transformer job 이 실행되어 Contrail 에 API call 을 실행하여 각 앱의 사설 네트워크에 설정을 적용해 줍니다.

Contrail은  Overlay Networking이라는 기술을 사용하여 위 작업을 실행합니다.  우리의 경우, Contrail은 GRE 터널을 사용하여 호스트 간 연결하며, Gateway router을 사용하여 터널과 나머지 네트워크의 트래픽 유입/유출관리를 합니다. OpenContrail 시스템은 표준 MPLS L3VPNS와 비슷한 개념을 가지고 있으며, 더 자세한 설계 자료는  여기에 있습니다.

우리가 이 시스템을 시작하면서, 몇 가지 문제점들을 만났습니다 :

  • Contrail and Docker의 통합(Integration)

  • rCluster 밖의 네트워크가 새로운 오버레이 네트워크에 원활하게 액세스 할 수 있도록 허용

  • 다른 cluster 간 앱 통신

  • AWS에 오버레이 네트워크 구축

  • 오버레이 네트워크에 HA edge-facing 앱 설치

CONTINUOUS DELIVERY

Max Stewart 의 포스팅 Riot’s 의Docker 사용법(continuous delivery) 은 rCluster에서도 적용됩니다.

Loot application의 CI flow 는 아래와 비슷합니다.:

여기서 목표는 마스터 repo가 변경되었을 때, 새로운 앱 container가 만들어져 QA환경에 deploy되어야 합니다. 위와 같은 프로세스로, 팀은 빠르게 코드를 적용하여 사용자에게 반영되도록 할 수 있습니다. 이와 같은 피드백 루프는 사용자 경험을 신속하게 개선 할 수 있게 해 주며, 이는 플레이어 중심 엔지니어링에 중요한 목표입니다.

DYNAMIC 앱 운용

이즘에서 우리는 Riot이 어떻게 구축하고 deploy 했는지 알 수 있습니다. 하지만 container 환경을  제대로 구축해보시면 알겠지만, 얘기하지 않은 더 많은 문제들이 있습니다.

rCluster 모델에서, containers는 동적 IP를 가지고 있으며, 지속적으로 바뀝니다. 이는 과거 고정된 서버를 사용한 것과 매우 다른 접근 방식이었으며, 제대로 운용하기 위해 새로운 도구와 프로세스가 필요합니다.

발견된 문제점들:

  • 어떻게 앱 모니터를 할 것인가? (계속 바뀌는 Endpoint&Capacity)

  • A앱이 바뀌는 B앱의 endpoint를 어떻게 알 수 있는가?

  • 어떻게 앱의 문제점을 발견할 것인가? (새로운 container 에 ssh 불가, log 리셋됨)

  • build 시 container를 만들려면, DB 비밀번호 설정 및 옵션값을 어떻게 줄 것인가?

위 문제를 해결하기 위해, 우리는 Microservices Platform 을 만들어 서비스  검색, 구성 및 모니터링을 처리합니다. 이는 글 시리즈의 마지막 즘에 다시 언급하겠습니다.

결론

이 글이 과거 Riot의 전반적인 문제들을 잘 전달했으면 좋겠습니다. 우리는 플레이어 가치를 위해 많은 노력을 했습니다. 이 글을 시작으로 시리즈로 글을 써나아갈 것이며 다음 글은 rCluster’s 의 스케줄링에 대해 이야기 할 것입니다. 아래 링크에 업데이트 하겠습니다.

2/1/17 GitLab.com Database Incident(한글번역)

출처 : Gitlab.com
Gitlab의 허락을 받고 번역하였습니다.
피드백은 자유롭게 부탁드립니다.

17년 1월 31일  Gitlab의 데이터베이스 중 하나가 문제가 발생했습니다. 그 결과 Gitlab.com의 6시간 분량의 데이터베이스 데이터(issues, merge, requests, users, comments, snippets, etc.)를 잃었습니다. Git/wiki 리포지토리 및 self-hosted-installations는 영향을 받지 않았습니다. Production 데이터를 잃는 것은 심각한 사건이기에, 빠른 시일 내 이와 같은 사태가 벌어진 5가지 이유와 조치 사항을 포스팅 하겠습니다.

업데이트 2월 1일 18:14 UTC: GitLab.com  – 온라인 (정상 작동 )

업데이트 포스팅을 하는 동시에, 6시간 전 데이터베이스 백업을 복구하고 있습니다. 즉, Gitlab.com이 정상 작동 시, 17:20 UTC 와 23:25 UTC 사이의 데이터 중 (projects, issues, merge requests, users, comments, snippets, etc.) 는 손실됩니다.

Git 데이터 (리포지토리 와 wikis), self-hosted instances of GitLab은 영향을 받지 않았습니다.

아래는 이번 사건을 간략하게 정리한 내용입니다. 조금 더 자세한 내용은  gitlab 즉각 조치사항(영문) 에서 확인 하실 수 있습니다.

최초 사건

2017/01/31 18:00 UTC에, 우리는 불량 사용자들이 Snippets(사용자 코드)을 대량으로 입력하여 데이터베이스를 불안정하게 만드는 것을 감지했습니다. 이후 문제점 파악 및 해결을 위해 조치를 시작했습니다.

2017/01/31 21:00 UTC에, 이 사건으로 인해 데이터베이스의 쓰기 기능이 일시적으로 마비되었으며, downtime을 초래했습니다.

조치 사항

  • IP address기반으로 불량 사용자 접근 제한
  • 리포지토리를 CDN으로 사용하여 47,000개 IP를 동일 아이디로 접근하는 User를 제거
  • Snippets를 대량 생산하는 불량 사용자 접근 제한

두번째 사건

2017/01/31 22:00 UTC에 – DB Replication의 지연이 심각해지면서 우리는 호출을 받았습니다. 이는 write 급상승으로 보조 DB가 제 때 처리 되지 않았기 때문에 발생했습니다.

조치 사항

  • db2 고장 조치 시도(약 4 GB 지연)
  • db2.cluster 가 replicate 거부하여 /var/opt/gitlab/postgresql/data 삭제(clean replication)
  • db2.cluster 가 max_wal_senders의 수치가 낮음을  이유로  db1연결을 거부함, 이 설정은  WAL (= replication) 제한을 위해 사용했음
  • 팀원-1db1의 max_wal_senders 을 32 로 변경 후  PostgreSQL 재시작
  • PostgreSQL – 에러 (많은 semaphores 가 열려있어 start 거부)
  • 팀원-1max_connections 을 2000 에서 8000으로 조정, PostgreSQL 시작 성공 (8000having been used for almost a year)
  • db2.cluster 여전히 repliacte 거부, 에러는 없어졌지만 아무 동작도 하지 않음
  • 여기서부터 좌절감이 들어오기 시작함. 팀원-1 이 퇴근 예정이었지만 갑자기 많은 문제들이 발생하면서 퇴근 지연

세번째 사건

2017/01/31 23:00경 팀원-1 은 pg_basebackup 이 작동하지 않는 이유로, PostgreSQL 의 데이터 디렉토리가 비었음에도 불구하고 존재한다고 생각하여, 삭제를 결정. 삭제 몇 초 후  db2.cluster.gitlab.com 가 아닌, db1.cluster.gitlab.com에서 삭제한 것을 인지함.
2017/01/31 23:27 팀원-1 이 삭제를 취소했지만,  300 GB 데이터 중 약 4.5 GB 만 유지 됨.

GitLab.com 을 중지할 수 밖에 없었으며,  Twitter에 공지함:

We are performing emergency database maintenance, http://GitLab.com  will be taken offline

Photo published for Code, test, and deploy together with GitLab open source git repo management software

발생했던 문제

  • LVM snapshots 이 매 24시간마다 찍힘. 팀원-1 이 사건 발생 6시간 전 DB 로드 밸런싱을 위해 수동으로  snapshot을 저장하였음.
  • Regular backups 또한 매 24시간마다 찍히지만, 팀원-1 은 그것이 어디에 저장되어 있는지 몰랐으며, 팀원-2 에 의하면 Regular backups은 제대로 작동하지 않았고, 몇 바이트만 저장됨.
  • 팀원-3: 제가 생각하기에 pg_dump 이 실행 되지 않은 이유로, PostgreSQL가 9.6 binaries버전이 아닌, 9.2 binaries 에서 실행되고 있었기 때문입니다. 이것은 data/PG_VERSION이 9.6으로 설정되었을 때, omnibus가 Pg 9.6만을 지원하기 때문입니다. 하지만 9.6파일이 존재하지 않았기에 9.2버전 파일에 접근하여 실행이 되지 않았습니다. 그 결과 SQL dumps는 만들어 지지 않았고, Fog gem은 과거 백업들을 삭제했을 것입니다.
  • Azure에 있는 NFS server를 대상으로 Disk snapshots가 실행되고  있었지만, DB servers에는 snapshots가 실행되고 있지 않음.
  • 동기화 프로세스에서 데이터 동기화 후 staging단계 도달 시, webhooks를 제거. 24간 전 Regular Backup에서 Pull을 하지 않으면 없어짐
  • Replication 프로세스가 너무 취약함, 에러에 민감, 랜덤한 shell scripts에 의지하고 있고 문서화가 안되어 있음
  • backups to S3 가 작동하지 않고 있음. (Bucket이 비어있음)
  • 즉, 5 backup/replication 기술이 deploy되었지만 그 중 하나도 제대로 작동하지 않고 있음. 결국 6 시간 전 백업을 사용해서 복구.
  • pg_basebackup 은 master가 replication프로세스를 시작하기 기다릴것, 다른 Prod 엔지니어에 의하면 10분 정도 소요됨.어떤이들은 이것을 멈춤 상태로 인지할 수 있음. 프로세스 진행 시 “strace” 상태로 무엇이 진행되는지 알 수 없음.

복구 작업

현재 우리는 staging DB에 있는 DB 백업을 통해 복구 중입니다.

We accidentally deleted production data and might have to restore from backup. Google Doc with live notes https://docs.google.com/document/d/1GCK53YDcBWQveod9kfzW-VCxIABGiryG7_z_6jHdVik/pub 

  • 2017/02/01 00:36 –  db1.staging.gitlab.com 데이터 백업
  • 2017/02/01 00:55 – Mount db1.staging.gitlab.com on db1.cluster.gitlab.com
  • StagingDB에 있는 /var/opt/gitlab/postgresql/data/ 를 productionDB /var/opt/gitlab/postgresql/data/ 에 복사
  • 2017/02/01 01:05 – nfs-share01 를 임시저장소로 사용 (/var/opt/gitlab/db-meltdown)
  • 2017/02/01 01:18 – 남은 production data 복사 ( pg_xlog 를 압축한 20170131-db-meltodwn-backup.tar.gz 포함)

아래 그래프는 데이터 삭제 시간 및 이후 데이터 복사 시간을 보여줍니다.