我们在Yandex.Cloud中接受10,000个事件。第1部分

大家好,朋友们!



*本文是基于开放车间REBRAIN&Yandex.Cloud写的,如果你喜欢看视频,你可以在此链接找到它- https://youtu.be/cZLezUm0ekE



最近,我们不得不接触Yandex.Cloud直播的机会。由于我们想长时间紧密地感受它,我们立即放弃了以云为基础推出一个简单的wordpress博客的想法-这太无聊了。经过深思熟虑,我们决定部署类似于服务生产体系结构的东西,以近乎实时的方式接收和分析事件。



我绝对确信,绝大多数在线(且不仅是)企业都以一种或另一种方式收集有关其用户及其行为的大量信息。至少对于做出某些决定(例如,如果您正在管理在线游戏)这是必要的,您可以看到用户最有可能卡住并删除玩具的级别的统计信息。还是为什么用户不购买任何东西就离开您的网站(您好,Yandex.Metrica)。



因此,我们的故事:我们如何用golang编写应用程序,如何测试kafka,rabbitmq和yqs,如何将数据流传输到Clickhouse集群以及如何使用yandex datalens可视化数据。自然地,所有这些都以docker,terraform,gitlab ci,当然还有prometheus的形式为基础设施带来了乐趣。我们走吧!



我想立即预约一下,因为我们不能一次就配置所有内容,为此,我们需要在本系列中发表几篇文章。关于结构的一些知识:



第1部分(正在阅读中)。我们将定义解决方案的技术规范和体系结构,并使用golang编写应用程序。

第2部分。我们将应用程序发布到生产环境中,使其可扩展并测试负载。

第三部分 让我们尝试找出为什么我们需要将消息存储在缓冲区中而不是文件中,并比较kafka,rabbitmq和yandex队列服务之间的原因。

第4部分 我们将部署Clickhouse集群,编写流以从那里的缓冲区传输数据,并在datalens中设置可视化。

第5部分 让我们将整个基础结构设置为适当的形状-使用gitlab ci配置ci / cd,使用prometheus和consul连接监视和服务发现。



传统知识



首先,我们将制定职权范围-我们到底想从输出中得到什么。



  1. 我们希望有一个event.kis.im形式的端点(kis.im是我们将在所有文章中使用的测试域),该端点应使用HTTPS接收事件。
  2. 事件是以下形式的简单json:{“ event”:“ view”,“ os”:“ linux”,“ browser”:“ chrome”}}。在最后阶段,我们将添加更多字段,但这不会发挥重要作用。如果愿意,可以切换到protobuf。
  3. 该服务应该能够每秒处理10,000个事件。
  4. 只需将新实例添加到我们的解决方案中,它就应该能够水平扩展。如果我们可以将前端移动到不同的地理位置以减少客户端请求的延迟,那就太好了。
  5. 容错能力。该解决方案必须足够稳定,并且能够在任何部件掉落(当然达到一定数量)时能够生存。


建筑



通常,对于此类任务,长期以来就发明了经典体系结构,使您可以有效地进行扩展。该图显示了我们解决方案的示例。







因此,我们拥有:



1.左侧是会生成各种事件的设备,无论是通过智能手机上玩具中的玩家等级还是通过常规浏览器在在线商店中创建订单。如TOR中所示,该事件是一个简单的json,发送到我们的端点-events.kis.im。



2.前两个服务器是简单的平衡器,它们的主要任务是:



  • . , , keepalived, IP .
  • TLS. , TLS . -, , -, , backend .
  • backend . — . , , load balancer’ .


3.在平衡器后面,我们有运行非常简单的应用程序的应用程序服务器。它应该能够接受传入的HTTP请求,验证发送的json并将数据存储到缓冲区中。



4.该图显示kafka作为缓冲区,尽管当然可以在此级别使用其他类似服务。我们将在第三篇文章中比较Kafka,rabbitmq和yqs。



5.我们架构的倒数第二个点是Clickhouse,这是一个列状数据库,可让您存储和处理大量数据。在这个级别上,我们需要将数据从缓冲区传输到实际上是存储系统(有关此内容,请参见第4条)。



这种安排使我们能够独立地水平缩放每一层。后端服务器无法应付-我们还要添加更多-因为它们是无状态的应用程序,因此,即使在自动模式下也可以做到。它不会以kafka的形式提取缓冲区-我们将添加更多服务器,并将本主题的某些分区转移给它们。 Clickhouse失败-这是不可能的:)实际上,我们还将删除服务器并共享数据。



顺便说一句,如果您想实现我们技术规范的可选部分并在不同地理位置进行缩放,那么没有什么比这容易的了:







在每个地理位置中,我们都使用应用程序和kafka部署负载均衡器。通常,有2个应用程序服务器,3个kafka节点和一个云平衡器(例如cloudflare)就足够了,它们将检查应用程序节点的可用性并根据客户端的原始IP地址通过地理位置进行平衡请求。因此,美国客户发送的数据将落在美国服务器上。还有来自非洲的数据-关于非洲。



然后一切都变得非常简单-我们使用kafka集中的镜像工具,并将所有位置的所有数据复制到位于俄罗斯的中央数据中心。在内部,我们解析数据并将其写入Clickhouse以进行后续可视化。



因此,我们确定了架构-让我们开始动摇Yandex.Cloud!



编写应用程序



在使用Cloud之前,您仍然必须忍受一点并编写一个相当简单的服务来处理传入事件。我们将使用golang,因为它已被证明非常出色,可作为编写网络应用程序的语言。



花了一个小时(也许几个小时)后,我们得到了这样的东西:https : //github.com/RebrainMe/yandex-cloud-events/blob/master/app/main.go



在此我要注意的重点是:



1.启动应用程序时,可以指定两个标志。一个负责我们将侦听传入的HTTP请求(-addr)的端口。第二个是我们将记录事件的-kafka服务器的地址(-kafka):



addr     = flag.String("addr", ":8080", "TCP address to listen to")
kafka    = flag.String("kafka", "127.0.0.1:9092", "Kafka endpoints”)


2.应用程序使用sarama库([] github.com/Shopify/sarama)将消息发送到kafka集群。我们立即设置针对最大处理速度的设置:



config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForLocal
config.Producer.Compression = sarama.CompressionSnappy
config.Producer.Return.Successes = true


3.此外,我们的应用程序具有内置的prometheus客户端,该客户端收集各种指标,例如:



  • 对我们应用程序的请求数量;
  • 执行请求时的错误数(无法读取发布请求,损坏的json,无法写入kafka);
  • 来自客户端的一个请求的处理时间,包括将消息写到kafka的时间。


4.我们的应用程序处理的三个端点:



  • /状态-只需返回确定即可表明我们还活着。虽然您可以添加一些检查,例如kafka群集的可用性。
  • /指标-根据此网址,Prometheus客户端将返回其已收集的指标。
  • /post — endpoint, POST json . json — -.


我会保留代码的完善性,因为它可以(而且应该!)完成。例如,您可以停止使用内置的net / http并切换到更快的fasthttp。或者,您可以通过将json验证检查进行到后续阶段来获得处理时间和cpu资源-何时将数据从缓冲区传输到Clickhouse集群。



除了问题的开发方面,我们立即考虑了未来的基础架构,并决定通过docker部署我们的应用程序。用于构建应用程序的最终Dockerfile是https://github.com/RebrainMe/yandex-cloud-events/blob/master/app/Dockerfile总的来说,这很简单,我要引起注意的唯一一点是多级组装,这使我们可以减少容器的最终图像。



云的第一步



首先,我们在cloud.yandex.ru上注册。填写所有必填字段后,我们将创建一个帐户并提供一定数量的赠款,以用于测试云服务。如果您想重复本文中的所有步骤,那么这笔拨款对您来说应该足够了。



注册后,将为您创建一个单独的云和默认目录,您可以在其中开始创建云资源。通常,在Yandex.Cloud中,资源之间的关系如下:







您可以为一个帐户创建多个云。在云内部,为不同的公司项目创建不同的目录。您可以在文档中阅读有关此内容的更多信息-https: //cloud.yandex.ru/docs/resource-manager/concepts/resources-hierarchy... 顺便说一句,在下文中,我将经常提到它。当我从头开始设置整个基础架构时,文档对我的帮助不只一次,因此建议您进行研究。



要管理云,可以同时使用Web界面和控制台实用程序-yc。使用一个命令执行安装(对于Linux和Mac Os):



curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash


如果内部安全人员对从Internet运行脚本感到愤怒,那么,首先,您可以打开脚本并阅读该脚本,其次,我们可以在我们的用户下运行该脚本-无需root权限。



如果要安装Windows客户端,则可以按照此处的说明进行操作,然后按照以下步骤yc init进行完全配置:



vozerov@mba:~ $ yc init
Welcome! This command will take you through the configuration process.
Please go to https://oauth.yandex.ru/authorize?response_type=token&client_id= in order to obtain OAuth token.

Please enter OAuth token:
Please select cloud to use:
 [1] cloud-b1gv67ihgfu3bp (id = b1gv67ihgfu3bpt24o0q)
 [2] fevlake-cloud (id = b1g6bvup3toribomnh30)
Please enter your numeric choice: 2
Your current cloud has been set to 'fevlake-cloud' (id = b1g6bvup3toribomnh30).
Please choose folder to use:
 [1] default (id = b1g5r6h11knotfr8vjp7)
 [2] Create a new folder
Please enter your numeric choice: 1
Your current folder has been set to 'default' (id = b1g5r6h11knotfr8vjp7).
Do you want to configure a default Compute zone? [Y/n]
Which zone do you want to use as a profile default?
 [1] ru-central1-a
 [2] ru-central1-b
 [3] ru-central1-c
 [4] Don't set default zone
Please enter your numeric choice: 1
Your profile default Compute zone has been set to 'ru-central1-a'.
vozerov@mba:~ $


原则上,此过程很简单-首先,您需要获取用于云管理的oauth令牌,选择云以及您将使用的文件夹。



如果同一云中有多个帐户或文件夹,则可以通过yc config profile profile create创建具有单独设置的其他配置文件,并在它们之间进行切换。



除了上述方法外,Yandex.Cloud团队还编写了一个非常好的terraform插件来管理云资源。就我而言,我准备了一个git存储库,在其中描述了将在本文框架内创建的所有资源-https: //github.com/rebrainme/yandex-cloud-events/。我们对master分支感兴趣,让我们在本地克隆它:




vozerov@mba:~ $ git clone https://github.com/rebrainme/yandex-cloud-events/ events
Cloning into 'events'...
remote: Enumerating objects: 100, done.
remote: Counting objects: 100% (100/100), done.
remote: Compressing objects: 100% (68/68), done.
remote: Total 100 (delta 37), reused 89 (delta 26), pack-reused 0
Receiving objects: 100% (100/100), 25.65 KiB | 168.00 KiB/s, done.
Resolving deltas: 100% (37/37), done.
vozerov@mba:~ $ cd events/terraform/


terraform中使用的所有主要变量都写入main.tf文件中。首先,请在terraform文件夹中创建一个private.auto.tfvars文件,其内容如下:



# Yandex Cloud Oauth token
yc_token = ""
# Yandex Cloud ID
yc_cloud_id = ""
# Yandex Cloud folder ID
yc_folder_id = ""
# Default Yandex Cloud Region
yc_region = "ru-central1-a"
# Cloudflare email
cf_email = ""
# Cloudflare token
cf_token = ""
# Cloudflare zone id
cf_zone_id = ""


所有变量都可以从yc config列表中获取,因为我们已经配置了控制台实用程序。我建议您立即将private.auto.tfvars添加到.gitignore,以免无意间发布私有数据。



在private.auto.tfvars中,我们还从Cloudflare中指定了数据-用于创建dns记录并将主域event.kis.im代理到我们的服务器。如果您不想使用cloudflare,请在main.tf和dns.tf文件中删除cloudflare提供程序的初始化,该文件负责创建必要的dns记录。



在我们的工作中,我们将结合所有三种方法-Web界面,控制台实用程序和terraform。



虚拟网络



老实说,可以跳过此步骤,因为当您创建新的云时,您将自动拥有一个单独的网络和3个子网-每个可用区一个。尽管如此,我还是想为我们的项目建立一个单独的网络,并使用自己的地址。 Yandex.Cloud中的网络操作的一般方案如下图所示(诚实地取自https://cloud.yandex.ru/docs/vpc/concepts/







因此,您创建了一个公共网络,资源可以在其中相互通信。对于每个可用性区域,将使用自己的地址创建一个子网,并将其连接到公共网络。结果,其中的所有云资源都可以通信,甚至位于不同的可用性区域中。连接到不同云网络的资源只能通过外部地址相互查看。顺便说一下,哈布雷(Habré)很好地描述了这种魔术在内部的运作方式



存储库中的network.tf文件中描述了网络创建。在那里,我们创建了一个共享的专用内部网络,并在不同的可用区域中将三个子网连接到内部-内部a(172.16.1.0/24),内部b(172.16.2.0/24),内部c(172.16.3.0/24) )。



我们初始化terraform并创建网络:



vozerov@mba:~/events/terraform (master) $ terraform init
... skipped ..

vozerov@mba:~/events/terraform (master) $ terraform apply -target yandex_vpc_subnet.internal-a -target yandex_vpc_subnet.internal-b -target yandex_vpc_subnet.internal-c

... skipped ...

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

yandex_vpc_network.internal: Creating...
yandex_vpc_network.internal: Creation complete after 3s [id=enp2g2rhile7gbqlbrkr]
yandex_vpc_subnet.internal-a: Creating...
yandex_vpc_subnet.internal-b: Creating...
yandex_vpc_subnet.internal-c: Creating...
yandex_vpc_subnet.internal-a: Creation complete after 6s [id=e9b1dad6mgoj2v4funog]
yandex_vpc_subnet.internal-b: Creation complete after 7s [id=e2liv5i4amu52p64ac9p]
yandex_vpc_subnet.internal-c: Still creating... [10s elapsed]
yandex_vpc_subnet.internal-c: Creation complete after 10s [id=b0c2qhsj2vranoc9vhcq]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.


优秀的!我们已经建立了网络,现在准备创建内部服务。



创建虚拟机



要测试该应用程序,足以创建两个虚拟机-我们将需要第一个虚拟机来构建和运行该应用程序,第二个-运行kafka,我们将使用它来存储传入的消息。然后,我们将创建另一台计算机,在其中配置prometheus来监视应用程序。



将使用ansible配置虚拟机,因此在启动terraform之前,请确保您具有最新的ansible版本之一。并使用ansible星系安装所需的角色:



vozerov@mba:~/events/terraform (master) $ cd ../ansible/
vozerov@mba:~/events/ansible (master) $ ansible-galaxy install -r requirements.yml
- cloudalchemy-prometheus (master) is already installed, skipping.
- cloudalchemy-grafana (master) is already installed, skipping.
- sansible.kafka (master) is already installed, skipping.
- sansible.zookeeper (master) is already installed, skipping.
- geerlingguy.docker (master) is already installed, skipping.
vozerov@mba:~/events/ansible (master) $


在ansible文件夹中,有一个我正在使用的示例.ansible.cfg配置文件。也许有用。



在创建虚拟机之前,请确保已运行ssh-agent并添加了ssh密钥,否则terraform将无法连接到创建的计算机。我当然遇到了OS X中的一个错误:https : //github.com/ansible/ansible/issues/32499#issuecomment-341578864为避免重复该故事,请在启动Terraform之前向环境添加一个小变量:



vozerov@mba:~/events/terraform (master) $ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES


在terraform文件夹中创建必要的资源:



vozerov@mba:~/events/terraform (master) $ terraform apply -target yandex_compute_instance.build -target yandex_compute_instance.monitoring -target yandex_compute_instance.kafka
yandex_vpc_network.internal: Refreshing state... [id=enp2g2rhile7gbqlbrkr]
data.yandex_compute_image.ubuntu_image: Refreshing state...
yandex_vpc_subnet.internal-a: Refreshing state... [id=e9b1dad6mgoj2v4funog]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

... skipped ...

Plan: 3 to add, 0 to change, 0 to destroy.

... skipped ...


如果一切顺利(应该如此),那么我们将拥有三个虚拟机:



  1. build-用于测试和构建应用程序的机器。Docker由ansible自动安装。
  2. 监视-用于监视的机器-已安装prometheus&grafana。登录名/密码是标准的:admin / admin
  3. kafka是安装了kafka的小型汽车,可在端口9092上使用。


让我们确保它们都就位:



vozerov@mba:~/events (master) $ yc compute instance list
+----------------------+------------+---------------+---------+---------------+-------------+
|          ID          |    NAME    |    ZONE ID    | STATUS  |  EXTERNAL IP  | INTERNAL IP |
+----------------------+------------+---------------+---------+---------------+-------------+
| fhm081u8bkbqf1pa5kgj | monitoring | ru-central1-a | RUNNING | 84.201.159.71 | 172.16.1.35 |
| fhmf37k03oobgu9jmd7p | kafka      | ru-central1-a | RUNNING | 84.201.173.41 | 172.16.1.31 |
| fhmt9pl1i8sf7ga6flgp | build      | ru-central1-a | RUNNING | 84.201.132.3  | 172.16.1.26 |
+----------------------+------------+---------------+---------+---------------+-------------+


资源到位,从这里我们可以拉出它们的ip地址。在下面的任何地方,我都将使用ip地址通过ssh连接并测试应用程序。如果您有一个连接到terraform的cloudflare帐户,请随时使用新创建的DNS名称。

顺便说一句,在创建虚拟机时,会发出内部ip和内部DNS名称,因此您可以通过名称引用网络中的服务器:



ubuntu@build:~$ ping kafka.ru-central1.internal
PING kafka.ru-central1.internal (172.16.1.31) 56(84) bytes of data.
64 bytes from kafka.ru-central1.internal (172.16.1.31): icmp_seq=1 ttl=63 time=1.23 ms
64 bytes from kafka.ru-central1.internal (172.16.1.31): icmp_seq=2 ttl=63 time=0.625 ms
^C
--- kafka.ru-central1.internal ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.625/0.931/1.238/0.308 ms


这对于我们使用kafk向应用程序指示端点很有用。



汇总应用程序



太好了,那里有服务器,还有一个应用程序-剩下的就是收集和发布它。对于程序集,我们将使用常规的docker构建,但作为映像存储,我们将从Yandex中获取服务-容器注册表。但是首先是第一件事。



将应用程序复制到构建计算机,转到ssh并收集映像:



vozerov@mba:~/events/terraform (master) $ cd ..
vozerov@mba:~/events (master) $ rsync -av app/ ubuntu@84.201.132.3:app/

... skipped ...

sent 3849 bytes  received 70 bytes  7838.00 bytes/sec
total size is 3644  speedup is 0.93

vozerov@mba:~/events (master) $ ssh 84.201.132.3 -l ubuntu
ubuntu@build:~$ cd app
ubuntu@build:~/app$ sudo docker build -t app .
Sending build context to Docker daemon  6.144kB
Step 1/9 : FROM golang:latest AS build
... skipped ...

Successfully built 9760afd8ef65
Successfully tagged app:latest


完成了一半的工作-现在您可以通过运行我们的应用程序并将其指向kafka来检查其功能:



ubuntu@build:~/app$ sudo docker run --name app -d -p 8080:8080 app /app/app -kafka=kafka.ru-central1.internal:9092</code>

      event    :

<code>vozerov@mba:~/events (master) $ curl -D - -s -X POST -d '{"key1":"data1"}' http://84.201.132.3:8080/post
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 13 Apr 2020 13:53:54 GMT
Content-Length: 41

{"status":"ok","partition":0,"Offset":0}
vozerov@mba:~/events (master) $


该应用程序以记录的成功作为响应,并指示分区的ID和消息所属的偏移量。唯一要做的就是在Yandex.Cloud中创建一个注册表并将其上传到那里(registry.tf文件中介绍了如何使用三行代码来完成此操作)。我们创建一个存储库:



vozerov@mba:~/events/terraform (master) $ terraform apply -target yandex_container_registry.events

... skipped ...

Plan: 1 to add, 0 to change, 0 to destroy.

... skipped ...

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.


在容器注册表中有几种验证方式-使用oauth令牌,iam令牌或服务帐户密钥。有关这些方法的更多信息,请参阅文档https://cloud.yandex.ru/docs/container-registry/operations/authentication我们将使用服务帐户密钥,因此我们创建一个帐户:



vozerov@mba:~/events/terraform (master) $ terraform apply -target yandex_iam_service_account.docker -target yandex_resourcemanager_folder_iam_binding.puller -target yandex_resourcemanager_folder_iam_binding.pusher

... skipped ...

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.


现在仍然有必要为他做一把钥匙:



vozerov@mba:~/events/terraform (master) $ yc iam key create --service-account-name docker -o key.json
id: ajej8a06kdfbehbrh91p
service_account_id: ajep6d38k895srp9osij
created_at: "2020-04-13T14:00:30Z"
key_algorithm: RSA_2048


我们获取有关存储ID的信息,翻转键并登录:



vozerov@mba:~/events/terraform (master) $ scp key.json ubuntu@84.201.132.3:
key.json                                                                                                                    100% 2392   215.1KB/s   00:00

vozerov@mba:~/events/terraform (master) $ ssh 84.201.132.3 -l ubuntu

ubuntu@build:~$ cat key.json | sudo docker login --username json_key --password-stdin cr.yandex
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
ubuntu@build:~$


要将图像加载到注册表中,我们需要ID容器注册表,我们从yc实用程序中获取它:



vozerov@mba:~ $ yc container registry get events
id: crpdgj6c9umdhgaqjfmm
folder_id:
name: events
status: ACTIVE
created_at: "2020-04-13T13:56:41.914Z"


之后,我们用新名称标记图像并加载:



ubuntu@build:~$ sudo docker tag app cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
ubuntu@build:~$ sudo docker push cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
The push refers to repository [cr.yandex/crpdgj6c9umdhgaqjfmm/events]
8c286e154c6e: Pushed
477c318b05cb: Pushed
beee9f30bc1f: Pushed
v1: digest: sha256:1dd5aaa9dbdde2f60d833be0bed1c352724be3ea3158bcac3cdee41d47c5e380 size: 946


我们可以确保映像成功启动:



vozerov@mba:~/events/terraform (master) $ yc container repository list
+----------------------+-----------------------------+
|          ID          |            NAME             |
+----------------------+-----------------------------+
| crpe8mqtrgmuq07accvn | crpdgj6c9umdhgaqjfmm/events |
+----------------------+-----------------------------+


顺便说一句,如果在Linux机器上安装yc实用程序,则可以使用以下命令



yc container registry configure-docker


用于docker设置。



结论



因此,我们完成了一项艰巨的任务:



  1. .
  2. golang, -.
  3. container registry.


在下一部分中,我们将继续进行有趣的事情-我们将应用程序倒入生产环境,并最终启动其负载。不要切换!



这种材料是在开放车间REBRAIN&Yandex.Cloud的视频:我们对Yandex的云接受每秒10,000个请求- https://youtu.be/cZLezUm0ekE





如果您有兴趣参加网上活动等,并要求实时的问题,连接通道REBRAIN开发的DevOps



在此特别感谢Yandex.Cloud举办这次活动的机会。指向它们的链接是https://cloud.yandex.ru/prices



如果您需要迁移到云或对基础架构有疑问,请随时提出请求



P.S. 2 , , .



All Articles