ablog

不器用で落着きのない技術者のメモ

マイクロサービスとは

(書きかけ)
マイクロサービスとは何か、原点を求め、2014年3月に Martin Fowler と James Lewis が書いたブログエントリ Microservices にあたってみた。

マイクロサービスとは

a definition of this new architectural term

The term "Microservice Architecture" has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services. While there is no precise definition of this architectural style, there are certain common characteristics around organization around business capability, automated deployment, intelligence in the endpoints, and decentralized control of languages and data.

Microservices

「マイクロサービスアーキテクチャ」という用語はそれぞれ単独でデプロイ可能なサービスの集合体からなるアプリケーションの設計手法として生まれた。アーキテクチャスタイルの正確な定義はありませんが、共通の特徴があります。ビジネス能力についての組織、自動デプロイ、インテリジェントなエンドポイント、言語やデータの非中央集権化などです。

In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

一言でいうと、マイクロサービスアーキテクチャは小さなサービスの集合体として一つのアプリケーションを開発するアプローチです。個々のサービスは独立したプロセスとして実行され、HTTP API のような軽量のメカニズムでやりとりを行います。それらのサービスはビジネスドメイン単位で開発され、個別に自動デプロイが可能です。これらのサービスの中央集権的な管理は最小限にとどめられ、異なるプログラミング言語が使われたり、異なるデータストレージ技術が使われたりします。

モノリシックアプリケーションとの比較

To start explaining the microservice style it's useful to compare it to the monolithic style: a monolithic application built as a single unit. Enterprise Applications are often built in three main parts: a client-side user interface (consisting of HTML pages and javascript running in a browser on the user's machine) a database (consisting of many tables inserted into a common, and usually relational, database management system), and a server-side application. The server-side application will handle HTTP requests, execute domain logic, retrieve and update data from the database, and select and populate HTML views to be sent to the browser. This server-side application is a monolith - a single logical executable[2]. Any changes to the system involve building and deploying a new version of the server-side application.

Microservices

マイクロサービススタイルを説明するには、モノリシックスタイルと比較するのが有効です。モノリシックアプリケーションは単一のユニットとしてビルドされます。一般的にエンタープライズアプリケーションは3つのパーツから構成されます。クライアントサイドユーザーインターフェース(HTMLやJavascript)、サーバサイドアプリケーション。サーバサイドアプリケーションは HTTP リクエストをハンドリングして、ドメインロジックを実行し、データベースからデータを取得して更新し、HTML のビューに参照したデータを埋めてブラウザに返します。このサーバサイドアプリケーションはモノリシックな単一の実行可能プログラムです。システムに対するあらゆる変更は新しいバージョンのサーバサイドアプリケーションのビルドとデプロイを伴います。

Such a monolithic server is a natural way to approach building such a system. All your logic for handling a request runs in a single process, allowing you to use the basic features of your language to divide up the application into classes, functions, and namespaces. With some care, you can run and test the application on a developer's laptop, and use a deployment pipeline to ensure that changes are properly tested and deployed into production. You can horizontally scale the monolith by running many instances behind a load-balancer.

Microservices

システムを構築するにあたってこのようなモノリシックスタイルは自然な方法です。リクエストをハンドリングする全てのロジックはシングルプロセスで実行され、使用言語の基本機能を使ってアプリケーションをクラス、関数、ネームスベースで分けることができます。開発者のラップトップでアプリケーションを実行してテストし、デプロイメントのパイプラインを使って適切にテストされ本番環境にデプロイされます。ロードバランサーのは以下で多くのインスタンスを実行してモノリシックアプリケーションを水平にスケールさせることができます。

Monolithic applications can be successful, but increasingly people are feeling frustrations with them - especially as more applications are being deployed to the cloud . Change cycles are tied together - a change made to a small part of the application, requires the entire monolith to be rebuilt and deployed. Over time it's often hard to keep a good modular structure, making it harder to keep changes that ought to only affect one module within that module. Scaling requires scaling of the entire application rather than parts of it that require greater resource.

Microservices

モノリシックアプリケーションでうまくいくこともありますが、フラストレーションを感じる人が増えています。特に、さらに多くのアプリケーションがクラウドにデプロイされるようになると。変更サイクルが密接に結びついていて、アプリケーションのごく一部を変更するときもモノリシックアプリケーション全体をビルドしてデプロイし直す必要があります。時間の経過とともに適切なモジュール構成を保つことが難しくなり、変更の影響範囲をモジュール内に留めることが難しくなります。スケールアウトする際もパーツ単位ではなく全てのアプリケーションでとなるので多くのリソースを消費します。
f:id:yohei-a:20200119061302p:plain

These frustrations have led to the microservice architectural style: building applications as suites of services. As well as the fact that services are independently deployable and scalable, each service also provides a firm module boundary, even allowing for different services to be written in different programming languages. They can also be managed by different teams .

We do not claim that the microservice style is novel or innovative, its roots go back at least to the design principles of Unix. But we do think that not enough people consider a microservice architecture and that many software developments would be better off if they used it.

Microservices

これらの不満がサービスの集合体としてアプリケーションを構築するマイクロサービススタイルへとつながりました。各サービスが単独でデプロイ可能でサービ単位でスケールアウトが可能であると同時に、厳格なモジュール境界を提供すつため、それぞれのサービスを異なるプログラミング言語でサービスを開発することが可能です。それらのサービスは異なるチームで管理することもできます。
マイクロサービススタイルは新しくも革新的でもありません、そのルーツは Unix の哲学に遡ります。 しかし、マイクロサービスアーキテクチャを検討している人は少ないですが、多くのソフトウェア開発ではマイクロサービススタイルを使ったほうがよいと考えています。

マイクロサービスの特徴

Characteristics of a Microservice Architecture
We cannot say there is a formal definition of the microservices architectural style, but we can attempt to describe what we see as common characteristics for architectures that fit the label. As with any definition that outlines common characteristics, not all microservice architectures have all the characteristics, but we do expect that most microservice architectures exhibit most characteristics. While we authors have been active members of this rather loose community, our intention is to attempt a description of what we see in our own work and in similar efforts by teams we know of. In particular we are not laying down some definition to conform to.

Microservices

マイクロサービスアーキテクチャの正式な定義はありませんが、共通する特徴をあげてみます。すべてのマイクロサービスアーキテクチャがすべての特徴を備えているわけではありませんが、ほとんどのマイクロサービスアーキテクチャがほとんどの特徴を備えていると思います。
我々著者はこのややゆるいコミュニティのアクティブなメンバーですが、私たちの意図は、我々自身や我々が知っているチームによる似たような試みについて説明することです。特に、準拠する定義は定めていません。

Componentization via Services
For as long as we've been involved in the software industry, there's been a desire to build systems by plugging together components, much in the way we see things are made in the physical world. During the last couple of decades we've seen considerable progress with large compendiums of common libraries that are part of most language platforms.

ソフトウェア業界に携わっている限り、物理的な世界で物が作られるのと同様に、コンポーネントをつなぎ合わせてシステムを構築したいという願望があります。過去数十年の間に、ほとんどの言語プラットフォームの一部である共通ライブラリの大幅な進歩がありました。

Graph Database の物理的なデータ構造

Graph Database の物理的なデータ構造の実装例がどうなっているか調べてみた。

Graph Databases: New Opportunities for Connected Data

Graph Databases: New Opportunities for Connected Data

の "Chapter 6. Graph Database Internals" の説明が分かりやすい。

Native Graph Processing
(中略)
A database engine that utilizes index-free adjacency is one in which each node maintains direct references to its adjacent nodes. Each node, therefore, acts as a micro-index of other nearby nodes, which is much cheaper than using global indexes. It means that query times are independent of the total size of the graph, and are instead simply proportional to the amount of the graph searched.

A nonnative graph database engine, in contrast, uses (global) indexes to link nodes together, as shown in Figure 6-1. These indexes add a layer of indirection to each traversal, thereby incurring greater computational cost. Proponents for native graph processing argue that index-free adjacency is crucial for fast, efficient graph traversals.


Neo4Jではソースコードはこのあたりだろうか。

package org.neo4j.io.fs;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class StoreFileChannel implements StoreChannel
{
    private final FileChannel channel;

    public StoreFileChannel( FileChannel channel )
    {
        this.channel = channel;
    }

(中略)

    @Override
    public int read( ByteBuffer dst, long position ) throws IOException
    {
        return channel.read( dst, position );
    }

    @Override
    public void readAll( ByteBuffer dst ) throws IOException
    {
        while ( dst.hasRemaining() )
        {
            int bytesRead = channel.read( dst );
            if ( bytesRead < 0 )
            {
                throw new IllegalStateException( "Channel has reached end-of-stream." );
            }
        }
    }

Amazon Linux 2 に pyenv をインストールする

  • パッケージをインストール
$ sudo yum -y install git \
  bzip2 \
  bzip2-devel \
  gcc \
  git \
  libffi-devel \
  make \
  openssl \
  openssl-devel \
  readline \
  readline-devel \
  sqlite \
  sqlite-devel \
  zlib-devel
  • pyenv をインストール
$ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
  • パスを通す
$ cat << 'EOS' >> ~/.bashrc
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
EOS
$ source ~/.bashrc
  • インストール可能な Python のバージョンを確認する
$ pyenv install --list
  • Python 3.7.5 をインストール
$ pyenv install 3.7.5
  • インストールした Python 3.7.5 が使われるよう設定する。
$ pyenv global 3.7.5
$ pyenv rehash
  • 有効になっている Python のバージョンを確認する
$ pyenv versions 
  system
* 3.7.5 (set by /home/ec2-user/.pyenv/version)

AWS Data Wrangler を使ってみる

$ sudo yum -y install python-pip
$ sudo pip install awswrangler
  • 実行してみる
$ python
>>> import awswrangler as wr
>>> list = wr.s3.list_objects('s3://amazon-reviews-pds/tsv/')
>>> from pprint import pprint
>>> pprint(list)
['s3://amazon-reviews-pds/tsv/',
 's3://amazon-reviews-pds/tsv/amazon_reviews_multilingual_DE_v1_00.tsv.gz',
 's3://amazon-reviews-pds/tsv/amazon_reviews_multilingual_FR_v1_00.tsv.gz',
 
(中略)
 
 's3://amazon-reviews-pds/tsv/sample_fr.tsv',
 's3://amazon-reviews-pds/tsv/sample_us.tsv']

AWS App Mesh とは

マイクロサービス間のやりとりにおいてビジネスロジックと関係ない汎用的機能をプロキシとして提供するのがサービスメッシュ。昔はライブラリとして提供している時代もあったが、汎用機能を変えたときに全アプリの再コンパイル・デプロイが不要、アプリと異なる言語で開発できる点でプロキシ型が優れている。このサービスメッシュのコントロールプレーンをマネージドサービスにしたのが AWS App Mesh である。例えば、Istio を使って自前でコントロールプレーンとデータプレーンの両方を構築できるが、AWS App Mesh を使うとコントロールプレーンはマネージドサービスに任せることができる。AWS App Mesh はコントロールプレーンで、実体は ECS や EKS、Route 53、AWS Cloud Map などで動作する。AWS App Mesh は構成・設定を注入し、実際の名前解決は Route53 や AWS Cloud Map、ロードバランシングやサーキットブレイキングは Envoy、モニタリングは X-Ray などのコンテナがサイドカーとして行う。サイドカーで近い場所になるのは性能面で優れている。

参考

AWS App Mesh Workshop をやってみた

AWS App Mesh Workshop :: Amazon App Mesh Workshop をやってみた。

$ mkdir -p ~/environment/scripts
$ cat > ~/environment/scripts/install-tools <<-"EOF"

#!/bin/bash -ex

sudo yum install -y jq gettext

sudo curl --silent --location "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm"
sudo yum install -y session-manager-plugin.rpm

sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/2.4.0/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
sudo curl --silent --location -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v1.13.7/bin/linux/amd64/kubectl
sudo chmod +x /usr/local/bin/kubectl

curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv -v /tmp/eksctl /usr/local/bin

if ! [ -x "$(command -v jq)" ] || ! [ -x "$(command -v envsubst)" ] || ! [ -x "$(command -v kubectl)" ] || ! [ -x "$(command -v eksctl)" ] || ! [ -x "$(command -v ssm-cli)" ]; then
  echo 'ERROR: tools not installed.' >&2
  exit 1
fi

pip install awscli --upgrade --user

EOF
  • 実行権限を付与する。
$ chmod +x ~/environment/scripts/install-tools
  • ツールをインストールする。
$ ~/environment/scripts/install-tools
$ sudo yum -y install git
$ cd ~/environment
$ git clone https://github.com/brentley/ecsdemo-frontend.git
$ git clone https://github.com/brentley/ecsdemo-nodejs.git
$ git clone https://github.com/brentley/ecsdemo-crystal.git
  • AWS CLI でデフォルトリージョンを設定する。
$ aws configure
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: us-west-2
Default output format [None]: 
  • CloudFormation テンプレートをダウンロードする。
cd ~/environment
curl -s https://raw.githubusercontent.com/brentley/appmeshworkshop/master/templates/appmesh-baseline.yml -o appmesh-baseline.yml
  • CloudFormation Template をデプロイする。
aws cloudformation deploy \
    --template-file appmesh-baseline.yml \
    --stack-name appmesh-workshop \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides Cloud9IAMRole=EC2Role
  • SSH key を作成する。
# Retrieve private key
aws ssm get-parameter \
  --name /appmeshworkshop/keypair/id_rsa \
  --with-decryption | jq .Parameter.Value --raw-output > ~/.ssh/id_rsa

# Set appropriate permission on private key
chmod 600 ~/.ssh/id_rsa

# Store public key separately from private key
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
  • bootstrap scripts を作成する。
# bootstrap script
cat > ~/environment/scripts/bootstrap <<-"EOF"

#!/bin/bash -ex

echo 'Fetching CloudFormation outputs'
~/environment/scripts/fetch-outputs
echo 'Building Docker Containers'
~/environment/scripts/build-containers
echo 'Creating the ECS Services'
~/environment/scripts/create-ecs-service
echo 'Creating the EKS Cluster'
~/environment/scripts/build-eks

EOF

# fetch-outputs script
cat > ~/environment/scripts/fetch-outputs <<-"EOF"

#!/bin/bash -ex

STACK_NAME=appmesh-workshop
aws cloudformation describe-stacks \
  --stack-name "$STACK_NAME" | \
jq -r '[.Stacks[0].Outputs[] | 
    {key: .OutputKey, value: .OutputValue}] | from_entries' > cfn-output.json

EOF

# Create EKS configuration file
cat > ~/environment/scripts/eks-configuration <<-"EOF"

#!/bin/bash -ex

STACK_NAME=appmesh-workshop
PRIVSUB1_ID=$(jq < cfn-output.json -r '.PrivateSubnetOne')
PRIVSUB1_AZ=$(aws ec2 describe-subnets --subnet-ids $PRIVSUB1_ID | jq -r '.Subnets[].AvailabilityZone')
PRIVSUB2_ID=$(jq < cfn-output.json -r '.PrivateSubnetTwo')
PRIVSUB2_AZ=$(aws ec2 describe-subnets --subnet-ids $PRIVSUB2_ID | jq -r '.Subnets[].AvailabilityZone')
PRIVSUB3_ID=$(jq < cfn-output.json -r '.PrivateSubnetThree')
PRIVSUB3_AZ=$(aws ec2 describe-subnets --subnet-ids $PRIVSUB3_ID | jq -r '.Subnets[].AvailabilityZone')
AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | grep region | cut -d\" -f4)

cat > /tmp/eks-configuration.yml <<-EKS_CONF
  apiVersion: eksctl.io/v1alpha5
  kind: ClusterConfig
  metadata:
    name: $STACK_NAME
    region: $AWS_REGION
  vpc:
    subnets:
      private:
        $PRIVSUB1_AZ: { id: $PRIVSUB1_ID }
        $PRIVSUB2_AZ: { id: $PRIVSUB2_ID }
        $PRIVSUB3_AZ: { id: $PRIVSUB3_ID }
  nodeGroups:
    - name: appmesh-workshop-ng
      labels: { role: workers }
      instanceType: m5.large
      desiredCapacity: 3
      ssh: 
        allow: false
      privateNetworking: true
      iam:
        withAddonPolicies:
          imageBuilder: true
          albIngress: true
          autoScaler: true
          appMesh: true
          xRay: true
          cloudWatch: true
          externalDNS: true
EKS_CONF

EOF

# Create the EKS building script
cat > ~/environment/scripts/build-eks <<-"EOF"

#!/bin/bash -ex

EKS_CLUSTER_NAME=$(jq < cfn-output.json -r '.EKSClusterName')

if [ -z "$EKS_CLUSTER_NAME" ] || [ "$EKS_CLUSTER_NAME" == null ]
then
  
  if ! aws sts get-caller-identity --query Arn | \
    grep -q 'assumed-role/AppMesh-Workshop-Admin/i-'
  then
    echo "Your role is not set correctly for this instance"
    exit 1
  fi

  sh -c ~/environment/scripts/eks-configuration
  eksctl create cluster -f /tmp/eks-configuration.yml
else

  NODES_IAM_ROLE=$(jq < cfn-output.json -r '.NodeInstanceRole')

  aws eks --region $AWS_REGION update-kubeconfig --name $EKS_CLUSTER_NAME
  cat > /tmp/aws-auth-cm.yml <<-EKS_AUTH
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: aws-auth
      namespace: kube-system
    data:
      mapRoles: |
        - rolearn: $NODES_IAM_ROLE 
          username: system:node:{{EC2PrivateDNSName}}
          groups:
            - system:bootstrappers
            - system:nodes
EKS_AUTH
  kubectl apply -f /tmp/aws-auth-cm.yml
fi

EOF

# build-containers script
cat > ~/environment/scripts/build-containers <<-"EOF"

#!/bin/bash -ex

CRYSTAL_ECR_REPO=$(jq < cfn-output.json -r '.CrystalEcrRepo')
NODEJS_ECR_REPO=$(jq < cfn-output.json -r '.NodeJSEcrRepo')

$(aws ecr get-login --no-include-email)

docker build -t crystal-service ecsdemo-crystal
docker tag crystal-service:latest $CRYSTAL_ECR_REPO:vanilla
docker push $CRYSTAL_ECR_REPO:vanilla

docker build -t nodejs-service ecsdemo-nodejs
docker tag nodejs-service:latest $NODEJS_ECR_REPO:latest
docker push $NODEJS_ECR_REPO:latest

EOF

# create-ecs-service script
cat > ~/environment/scripts/create-ecs-service <<-"EOF"

#!/bin/bash -ex

CLUSTER=$(jq < cfn-output.json -r '.EcsClusterName')
TASK_DEF=$(jq < cfn-output.json -r '.CrystalTaskDefinition')
TARGET_GROUP=$(jq < cfn-output.json -r '.CrystalTargetGroupArn')
SUBNET_ONE=$(jq < cfn-output.json -r '.PrivateSubnetOne')
SUBNET_TWO=$(jq < cfn-output.json -r '.PrivateSubnetTwo')
SUBNET_THREE=$(jq < cfn-output.json -r '.PrivateSubnetThree')
SECURITY_GROUP=$(jq < cfn-output.json -r '.ContainerSecurityGroup')

aws ecs create-service \
  --cluster $CLUSTER \
  --service-name crystal-service-lb \
  --task-definition $TASK_DEF \
  --load-balancer targetGroupArn=$TARGET_GROUP,containerName=crystal-service,containerPort=3000 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration \
      "awsvpcConfiguration={
        subnets=[$SUBNET_ONE,$SUBNET_TWO,$SUBNET_THREE],
        securityGroups=[$SECURITY_GROUP],
        assignPublicIp=DISABLED}"

EOF
  • 実行権限を付与する。
$ chmod +x ~/environment/scripts/*
  • Docker をインストールして起動する。
$ sudo yum -y install docker
$ sudo /bin/systemctl start docker.service
  • AWS CLI をアップデートする。
$ sudo su -
# curl -kL https://bootstrap.pypa.io/get-pip.py | python
# pip install -U awscli
$ vi ~/environment/scripts/build-eks
#  if ! aws sts get-caller-identity --query Arn | \
#    grep -q 'assumed-role/AppMesh-Workshop-Admin/i-'
#  then
#    echo "Your role is not set correctly for this instance"
#    exit 1
#  fi
  • bootstrap を実行する。
$ ~/environment/scripts/bootstrap
  • 以下を実行して表示されたURLにブラウザでアクセスする。
$ echo "http://$(jq -r '.ExternalLoadBalancerDNS' cfn-output.json)/"