Author Archives: adachin

~CircleCIでTerraformリリースのサーバーレス化~ インフラの独自リリースを継続的リリースへ

adachin|2021年10月25日
SRE

SREチームの安達(@adachin0817)です。以前エンジニアブログにてこんなことを言っていたのを皆様覚えておりますでしょうか。

それ以外のプロジェクトであるLancers CreativeProsheetLancers AgentMENTAはECS/Fargateに移行しているので、すべてTerraformでコード管理しています。また、CircleCIでTerraform CI(validate,plan)を実行しているということもあり、今後terraform applyもCircleCIで行う予定なので、Deployサーバーでの運用は廃止していく予定です。

  • 今までのTerraform管理方法

そうです。以下のようにTerraformでの運用について課題がありました。

  • わざわざサーバーを用意したくないのと運用コストが上がる
  • Deployサーバーで管理するのはやめましょうといってもローカルPCで反映したくない
  • Terraformの適用(apply)はCircleCIのみでリリース(サーバーレス)ができればベスト

ちなみにDeployサーバーというのはランサーズで利用している各サーバー側のデプロイ(Ansible)やアプリケーションのデプロイと様々な用途として利用しています。またランサーズで運用しているEC2はAmazon Linux2のarmに移行後、性能評価を比較して、ECS/Fargateに移行する予定です。なのでCircleCIのみでのリリースに統一となると、Deployサーバーは廃止する方向になります。そこで今回は上記をどのように改善していったのかブログしていきたいと思います。

背景

  • Deployサーバーで適用してしまうとブランチの切り替えを忘れて他のリリースに影響が出る
  • Deployサーバーに依存してしまうので複数人でのTerraform開発スピードが遅れる
  • Terraformのバージョンアップ対応中でも急に依頼が来て、バージョンアップの切り替えをしなければならない
  • ECS Scheduled Taskによる頻繁に開発メンバーからの更新とそれに伴いリリースの際SREチームの工数を取ってしまう

上記の4つが課題となります。まず問題なのが、Deployサーバーに依存してしまうということです。もちろんTerraformでの開発はterraform planだけでは判断できないことも多く、コード書く際にその場で適用し、デバッグをして修正といったことも多々あります。そこでローカルPCで管理してしまうとクレデンシャルを設定しなければならなく、非常に強い権限が必要なため、万が一、漏洩となることも考えると、なるべくローカルにはクレデンシャルを設置したくないという思いがありました。またECS Scheduled TaskによるバッチはTerraform化しているので、頻繁に開発メンバーからはレビューとマージ後にSREチームの誰かが適用しなければなりません。そういった運用工数も取られてしまうので自動化できればと考えておりました。そして考慮した結果、Terraform専用の開発環境を構築し、CircleCIで継続的にリリースをするという方向になりました。

Terraform開発環境

  • ディレクトリ構成
tree .
.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── root
│   └── dev-credentials
└── setting_dev.sh
  • docker-compose.yml
version: '3'
services:
  app:
    container_name: menta-terraform-dev
    build:
      context: .
      dockerfile: Dockerfile
    image: menta-terraform-dev
    volumes:
      - ~/hoge:/var/www/hoge/:delegated
    tty: true
  • Dockerfile
FROM hashicorp/terraform:1.0.5
ENV APP_ROOT /var/www/hoge
WORKDIR $APP_ROOT

# Setup UTC+9
RUN apk --update add tzdata && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
apk del tzdata && \
rm -rf /var/cache/apk/*

# install packages
RUN apk update && \
apk upgrade && \
apk add --update --no-cache \
bash \
coreutils \
vim \
python3 \
py3-pip

# install aws-cli
RUN pip3 install --upgrade pip
RUN pip3 install awscli && rm -rf /var/cache/apk/*

# copy empty credentials
RUN mkdir /root/.aws
COPY root/dev-credentials /root/.aws/credentials
COPY root/.bashrc /root/.bashrc

ENTRYPOINT ["/bin/bash"]
  • setting_dev.sh
#!/bin/bash

aws --profile=terraform-kms s3 cp s3://hoge/dev.key /root/.aws/

aws --profile=hoge kms decrypt \
--ciphertext-blob fileb:///root/.aws/dev.key \
--output text \
--query Plaintext \
| base64 --decode > /root/.aws/newcredentials

mv /root/.aws/newcredentials /root/.aws/credentials

まず各プロジェクトにTerraformの開発環境を準備しました。TerraformはDocker hub公式でコンテナが用意されているので現状のv1.0.5を指定し、API経由ということもあってDockerでのPortも特に開放する必要がありません。

そしてクレデンシャルをどう管理するのかと考慮したところ、AWS KMSを利用してKMS専用のクレデンシャルから暗号化されたkeyをコピーし、新たにTerraformで利用するクレデンシャルを上書きで復号するようにシェルスクリプトで作成しました。コンテナ内にクレデンシャルを配置することで、セキュリティレベルが二重になり、リスクも軽減されるようになります。もちろんdocker-compose downをすればクレデンシャルは初期化されるので漏洩してしまう恐れも減ります。この開発環境の構築により、各々ローカルPCでTerraformのデバッグやバージョンアップがしやすい環境となりました。次はCircleCIでのリリースフローを説明していきましょう。

CircleCIでのリリースフロー(MENTA)

  • .circleci/config.yml
version: 2.1

setup: true

orbs:
  aws-ecr: circleci/aws-ecr@7.0.0
  aws-ecs: circleci/aws-ecs@2.2.0
  aws-cli: circleci/aws-cli@2.0.0
  path-filtering: circleci/path-filtering@0.0.3
  slack: circleci/slack@3.4.2

parameters:
deploy_app_stg:
type: boolean
default: false
deploy_app_private:
type: boolean
default: false

~省略~

workflows:
  version: 2
  ci:
    jobs:
      - laravel_test
      - stg_terraform_fmt_validate
      - stg_terraform_plan:
          requires:
            - stg_terraform_fmt_validate
      - prd_terraform_fmt_validate
      - prd_terraform_plan:
          requires:
      - prd_terraform_fmt_validate
      - path-filtering/filter:
          base-revision: origin/master
          config-path: .circleci/deploy.yml
          mapping: |
            terraform/stg/.* stg_terraform_apply true
            terraform/prd/.* prd_terraform_apply true

MENTAの例ですが、TerraformのCIはvalidate,planをpush時に実行しています。以前masterマージでterraform applyという運用をしていましたが、SREチームがTerraformのデバッグをして、そのままapplyをしたままマージせずに他のプルリクをマージすることで元に戻ってしまうということがあり、更に他の部分で差分が出ているのに気づかず、applyされて変更されてしまったりと、危うくインシデントになりそうだったのでapplyの部分は一旦断念しました。またCircleCIのAPIを利用してシェルでリリースをするという方法も考えましたが、権限的に開発メンバーが全員リリース出来てしまうという懸念点もありました。

そこで解決してくれるのが、CircleCIのpath-filteringというOrbsを利用して、terraformディレクトリにmasterとの差分があればapplyされるように実装しました。

https://circleci.com/developer/orbs/orb/circleci/path-filtering

path-filteringはsetup: true が付いていることによって複数のworkflowが無効化になります。その場合デプロイ系は全て別ymlに移行(deploy.yml)して、single workflowに変更する必要があります。single workflowになったことでCIが20秒ほど早くなりました。Terraform CIに関しては私の個人ブログを参考にしてみてください。

[AWS]CircleCIでTerraformのCI/CD環境を実装してみた

  • .circleci/deploy.yml
version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@7.0.0
  aws-ecs: circleci/aws-ecs@2.2.0
  aws-cli: circleci/aws-cli@2.0.0
  slack: circleci/slack@3.4.2

parameters:
stg_terraform_apply:
type: boolean
default: false
prd_terraform_apply:
type: boolean
default: false
deploy_app_stg:
type: boolean
default: false
deploy_app_private:
type: boolean
default: false

~省略~
jobs:
  check_terraform_difference:
    <<: *default_config
    steps:
      - run:
         name: install package
         command: |
           apk add bash curl jq
      - run: echo "No terraform difference ok !!"
      - slack/status:
          fail_only: true
          mentions: 'here'
          failure_message: 'Error check terraform difference 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}'
          webhook: ${SLACK_WEBHOOK}
      - slack/notify:
          title: 👍
          color: '#42f486'
          message: 'No terraform difference OK !! ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}'
          webhook: ${SLACK_WEBHOOK}
~省略~

workflows:
  version: 2.1
  check_terraform_difference:
    jobs:
     - check_terraform_difference
    when:
      and:
        - not: << pipeline.parameters.stg_terraform_apply >>
        - not: << pipeline.parameters.prd_terraform_apply >>
  stg_terraform_apply:
    when: << pipeline.parameters.stg_terraform_apply >>
    jobs:
      - stg_terraform_apply:
          filters:
            branches:
              only: master
  prd_terraform_apply:
    when: << pipeline.parameters.prd_terraform_apply >>
    jobs:
      - prd_terraform_apply:
          filters:
            branches:
              only: master
  deploy_app_stg:
    when: << pipeline.parameters.deploy_app_stg >>
    jobs:
      - deploy_app_stg
  deploy_app_private:
    when: << pipeline.parameters.deploy_app_private >>
    jobs:
      - deploy_app_private
  deploy_app_prd:
    jobs:
      - deploy_app_prd:
          filters:
            branches:
              only: master

新しいjobとしてcheck_terraform_differenceを作りましたが、CircleCIの仕様で差分がないときにmappingに検知されず、全てのパラメータはdefaultのfalseが返ってきてしまうという動作になってしまうため、実行するworkflowがない場合はエラーとなります。なのでechoでtrueを返すように作りました。ここはCircleCIさんぜひデフォでtrueを返すようにしてほしい!以下動作確認ですが、terraformディレクトリに差分がなければcheck_terraform_differenceが動作してそのままCIが動き、差分があればterraform applyされるようになりました。

  • 動作確認

まとめ

これでようやくDeployサーバーからの管理を脱却することができ、開発環境でのデバッグとTerraformのリリースを安全にサーバーレスでの実現や改善することができました。またLancers本体も完全Terraform化するための準備も整うことができました。CircleCIのpath-filteringはモノレポでもパス単位で自動リリースすることができるので、Lambdaなどにも応用ができることでしょう。またpath-filteringの今後のバージョンにも期待です!また課題等出てきたらブログしようと思います。

SRE募集しているので気になる方はぜひTwitterでDMお待ちしています!

※CircleCIユーザーコミュニティでLTしてきました!

開発/Stg環境のための本番DBマスキングと継続的リストアの仕組みを作りました

adachin|2021年09月06日
SRE

SREチームの安達(@adachin0817)です。今回はMENTALancers CreativeLancers Agencyマスキングした本番環境のデータをStgや開発環境のMySQLコンテナへ毎週リストアする仕組みを実装しました。実際にここらへんは運用をしていく中で一苦労されている方も多いのではないでしょうか。それではまず背景と、実装するに当たっての活動含めてご紹介できればと思います。

背景

今回はMENTAを例にしています。各サービスの開発環境はDockerを利用しており、本番とStg環境はTerraformで管理しています。カラム追加ではマイグレーションを実行することでサンプルのスキーマファイルを投入して開発をしているのですが、たまに開発環境で動いていたソースがStgや本番で動かないといったことで開発効率が下がることが見受けられます。開発メンバーにとってはより本番環境に近い環境でテストをすることがベストです。また、各AWSのアカウントは本番環境とStg環境を分離しています。もちろん気楽にRDSのクローン機能を利用して即座に同じDBを構築することができないですし、クローンだと本番データが入っているためインシデントが起こる可能性があります。また、運用上再作成することも踏まえると時間もかかるのと工数もかかります。そこでちょっとした工夫と設計を考える必要があり、ランサーズ本家と同様に継続的にリストアできる仕組みを今回実装しました。

※以下スペースマーケットさんと以前LT大会をした際にMENTAのAWS移行について登壇したのでぜひ参考にしてください。

[スペースマーケット×ランサーズ][SRE]第2回目のコラボイベント開催しました!

 

auto-masking

・環境

  • EC2(ディスク100GB)
  • Local DB(MySQL 8.0.26)
  • RDS Aurora
  • S3

・my.cnf

[mysqld]

disable_log_bin
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

character_set_server=utf8mb4
collation-server=utf8mb4_bin

skip-grant-tables
innodb_file_per_table=ON
innodb_buffer_pool_size=4G
innodb_log_file_size=1G
join_buffer_size=256K
read_buffer_size=1M
read_rnd_buffer_size=2M
sort_buffer_size=4M
max_heap_table_size=16M
tmp_table_size=16M
thread_cache_size=100
wait_timeout = 86400
max_allowed_packet = 1G
innodb_buffer_pool_size = 1G
max_allowed_packet = 16M
innodb_strict_mode = 0
long_query_time = 5

sql-mode = ''

[mysql]
default-character-set=utf8mb4

今回本番DBマスキングからStgへリストアするまでauto-maskingと名付けました。サーバー(devops)はコンテナではなく、EC2を選択しました。初期はECS/Fargateで挑戦しようと試みていましたが、現時点のエフェメラルストレージは200GBのみ対応しており、今後データが増える見込みであればストレージの拡張はEFSを利用するしかありません。もちろんEFSはコストが高いのと、エフェメラルストレージの変更はTerraformではサポートされておらず、aws-cliを利用して変更するしかありません。と考慮するとEC2で運用するのがふさわしいと考えました。

また、devopsサーバーのローカルDBはマスキング用のSQLを流すために必要であり、先のことも考えてMySQL 8.0.26を利用しました。各サービスのmy.cnfはAnsibleで共通にしています。マスキング用のSQLは開発メンバーとやり取りさせてもらいながら、個人情報を含むものはUPDATEして全て書き換えるように作り込みをしました。DB Dump先ですが、RDSのRead ReplicaからDumpをすることによりクラスターDBに影響がなくなります。ポイントとしては–skip-lock-tables –single-transactionのオプションを追加してREAD LOCKをさせないようにしましょう。ちなみにMySQL 8.0から8.0以前のバージョンのみ、Dump時に–skip-column-statisticsを指定することでANALYZE TABLE文の自動生成をなくすことでエラーの回避ができます。 これはMySQL 8.0以降ではオプティマイザがヒストグラム統計というものを考慮するようになったからになります。

それでは実際のシェルスクリプトのコードと流れを記載しておきます。

・prd-cron

$ crontab -l
#Ansible: cron prd-masking.sh
0 0 * * 6 /var/www/hoge/scripts/auto-masking/prd-masking.sh > /var/log/auto-masking/auto-masking.log 2>&1

・prd-masking.sh

#!/bin/bash

set -eu

$("/home/hoge/.common/secrets.sh")

DB=hoge
MIGRATIONFILE01=masking.sql

# git pull
git -C /var/www/hoge/scripts/auto-masking pull
rm -rf ~/backup/*

# dump RDS prd
echo "dump prd"
mysqldump -v --skip-column-statistics --skip-lock-tables --single-transaction --default-character-set=utf8mb4 --set-gtid-purged=OFF -h "${PROD_HOST}" -u "${PROD_USER}" -p"${PROD_PWD}" "${DB}" | gzip > ~/backup/prd-bk.gz

# local db restore
echo "local drop/create db"
mysql -h localhost -u root -e "drop database hoge;"
mysql -h localhost -u root -e "create database hoge;"

echo "local restore db"
zcat ~/backup/prd-bk.gz | mysql -h localhost -u root "${DB}" -f

# masking local db
echo "masking local db"
mysql -h localhost -u root "${DB}" < /var/www/hoge/masking/${MIGRATIONFILE01}

# dump local masking db
echo "local dump db"
mysqldump --skip-column-statistics --skip-lock-tables --single-transaction --default-character-set=utf8mb4 --routines=0 --triggers=0 --events=0 --set-gtid-purged=OFF -h localhost -u root "${DB}" | gzip > ~/masking_db/mask-bk.gz

# s3 copy s3
echo "copy masking db s3"
aws s3 cp ~/masking_db/mask-bk.gz s3://xxxxxxxxxxxxxxxxxxxx/mysql/masking/

echo "delete dir backup masking_db"
rm -rf ~/backup/*
rm -rf ~/masking_db/*

## slack notify
/var/www/hoge/scripts/auto-masking/post_slack.sh "Prd masking OK. Please wait restore Stg."
  • 毎週土曜日深夜0時に本番 devopsサーバーにてRDS(Read)からDump
  • 本番 devopsサーバーのローカルDBにリストア
  • 本番 devopsサーバーのローカルDBに対してマスキングSQLを適用
  • 本番 devopsサーバーのローカルDBをDump
  • StgのS3バケットにDumpファイルをコピー

・stg-cron

$ crontab -l
#Ansible: cron stg-masking-restore.sh
0 5 * * 6 /var/www/hoge/scripts/auto-masking/stg-masking-restore.sh > /var/log/auto-masking/auto-masking.log 2>&1

・stg-masking-restore.sh

#!/bin/bash

set -eu

$("/home/hoge/.common/secrets.sh")
DB=hoge

# git pull
git -C /var/www/hoge/scripts/auto-masking pull
rm -rf ~/masking_db/*

# s3 local copy s3
echo "copy masking db s3 local"
aws s3 cp s3://xxxxxxxxxxxxx/mysql/masking/mask-bk.gz ~/masking_db/

# restore masking stg RDS
echo "stg drop/create db"
mysql -h "${STG_HOST}" -u "${STG_USER}" -p"${STG_PWD}" -e "drop database hoge;"
mysql -h "${STG_HOST}" -u "${STG_USER}" -p"${STG_PWD}" -e "create database hoge;"

echo "stg restore db"
zcat ~/masking_db/mask-bk.gz | mysql -h "${STG_HOST}" -u "${STG_USER}" -p"${STG_PWD}" "${DB}" -f

echo "delete dir masking_db"
rm -rf ~/masking_db/*

## slack notify
/var/www/hoge/scripts/auto-masking/post_slack.sh "Stg restore OK"
  • 毎週土曜日5時にStg devopsサーバーからS3バケットに本番DBマスキングしたDumpファイルをコピー
  • Stg devopsサーバーから本番DBマスキングしたDumpファイルをStg用RDSにリストア

・Slack nofity

 

auto-renew-mysql-container

・環境

  • AWS CodeBuild
  • CloudWatch Events Rule
  • AWS ECR
  • docker hub
  • GitHub

次は開発環境のMySQLコンテナをリストアして自動化をします。(auto-renew-mysql-container) ここでは開発環境で利用しているMySQLのDockerfileを使いたいため、GitHubのリポジトリを連携したく、AWS CodeBuildとCloudWatch Events Ruleを利用して実装しました。毎週土曜日のAM9:00にマスキングされた本番DBのデータをMySQLコンテナにbuildしてリストアを行い、ECRにPushします。ちなみにCodeBuild上のインスタンスからMySQLコンテナを何度もpullしてしまうと以下Rate Limitsで制限がかかる仕様となりました。必ずdocker loginをしましょう。

https://www.docker.com/increase-rate-limits

また、CodebuildはTerraform化をしておらず、CloudWatch Events RuleのみTerraform化を行いました。以下buildspec.ymlとTerraformのコードを記載しておきます。

  • buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      docker: 18
    commands:
      - yum update -y && yum install -y curl
  pre_build:
    commands:
      - echo Login to AWS ECR...
      - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
      - echo Logging in to Docker Hub...
      - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin
      - cp -rp docker/dev/mysql/ /tmp/
      - cd docker/dev/mysql
      - docker build --no-cache -t mysql-dev:$IMAGE_TAG .
      - docker run -v /tmp:/tmp --name mysql-dev -d mysql-dev:$IMAGE_TAG
  build:
    commands:
      - hostname -i
      - docker ps
      - while true; do docker exec -i mysql-dev mysqladmin -u root ping || (sleep 10; false) && break; done
      - for i in $(find /tmp/mysql/ -name "*.sql"); do docker exec -i mysql-dev mysql -u root < $i ; done
      - docker exec -i mysql-dev mysql -u root -e 'create database hoge'
      - docker exec -i mysql-dev mysql -u root -e 'show databases'
      - aws s3 cp s3://xxxxxxxxxxxxx/mysql/masking/mask-bk.gz /tmp/
      - gunzip /tmp/mask-bk.gz
      - docker exec -i mysql-dev mysql -u root hoge < /tmp/mask-bk
  post_build:
    commands:
      - docker commit mysql-dev $IMAGE_REPO_NAME:$IMAGE_TAG
      - echo Push image to Amazon ECR...
      - docker push $IMAGE_REPO_NAME:$IMAGE_TAG
      - cd ../../../
      - bash ./scripts/auto-masking/post_slack.sh "Dev MySQL container restore OK"
  • codebuild.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codebuild:StartBuild"
            ],
            "Resource": [
                "arn:aws:codebuild:ap-northeast-1:xxxxxxxxxx:project/auto-renew-mysql-container"
            ]
        }
    ]
}
  • terraform/stg/iam.tf
## cloudwatch_events_codebuild_role
resource "aws_iam_role" "cloudwatch_events_codebuild_role" {
  name = "cloudwatch_events_codebuild_role"
  assume_role_policy = file("files/assume_role_policy/ecs-scheduled-tasks.json")
}

resource "aws_iam_policy" "cloudwatch_events_codebuild_policy" {
  name = "cloudwatch_events_codebuild_policy"
  description = "cloudwatch_events_codebuild_policy"
  policy = file("files/assume_role_policy/codebuild.json")
}

resource "aws_iam_role_policy_attachment" "cloudwatch_events_codebuild_attach" {
  role = aws_iam_role.cloudwatch_events_codebuild_role.name
  policy_arn = aws_iam_policy.cloudwatch_events_codebuild_policy.arn
}
  • terraform/stg/cloudwatch_event_target.tf
# auto_renew_mysql_container
resource "aws_cloudwatch_event_rule" "auto_renew_mysql_container" {
  name = "auto_renew_mysql_container"
  description = "auto_renew_mysql_container"
  schedule_expression = "cron(0 0 ? * SAT *)"
  is_enabled = "true"
}

resource "aws_cloudwatch_event_target" "auto_renew_mysql_container" {
  target_id = "auto_renew_mysql_container"
  arn = "arn:aws:codebuild:ap-northeast-1:xxxxxx:project/auto-renew-mysql-container"
  rule = aws_cloudwatch_event_rule.auto_renew_mysql_container.name
  role_arn = aws_iam_role.cloudwatch_events_codebuild_role.arn
}
  • 作成されたロールにAmazonEC2ContainerRegistryPowerUser、S3の権限を付与
  • Slack nofity

開発環境 MySQLコンテナ更新スクリプト

  • docker-update.sh
#!/bin/bash

## update MySQL container
aws ecr get-login-password --profile ecr | docker login --username AWS --password-stdin xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
cd ~/www/hoge/docker/dev/
docker-compose pull && docker image prune -f && docker-compose up -d
docker exec -it menta-app php artisan migrate

## update App container
docker stop menta-app-6 && docker rm menta-app
docker rmi menta-app
docker image prune -f && docker-compose up -d

最後に開発環境のMySQLコンテナは毎週各自更新する必要があるので、専用のシェルスクリプトを作成することによって手動での漏れを減らしました。

これにて実装は完了となります。


まとめ

実装内容がとにかくシビアで、疲労困憊でございます。たくさんの時間を使ってテストをしたおかげで継続的にリストアできる仕組みを作れたと感じています。開発メンバーからは非常にありがたいというお言葉もいただけたので、より良い環境を提供できたと思っています。また、現在本番DBの容量はまだまだ少ないので、今後データが増加していく場合、開発環境のMySQLコンテナも重くなってしまうので、データを一部分削る専用のTRUNCATE SQLを作成する必要があります。他にいいやり方あれば教えて下さい!

EC2で運用している分析基盤サーバー(Digdag + Embulk)をECS/Fargateに移行しました

adachin|2021年06月23日
SRE

SREチームの安達(@adachin0817)です。最近ではランサーズ本家のインフラをコンテナに移行しまくっております。今回ランサーズとMENTAで運用しているEC2/分析基盤サーバー(Digdag + Embulk)をECS/Fargateに移行完了しました。では早速概要と苦労した点、今後の展望などを振り返っていきたいと思います。

分析基盤の紹介

ランサーズの分析基盤(capybara)と運用について紹介

MENTAをAWSに移行しました

ちなみに私が入社して3年経つのですが、運用して変わったことは3年前よりデータの量が膨大になっていることと、現在、社内の分析チームにとって欠かせないシステムとなっております。その中でDigdagによるスケジューラーとEmbulkによるマルチソースバルクデータローダーである分析基盤専用のEC2サーバーがあり、毎日夜中にデータをBigQuryにシンクしています。もちろんMENTAも同様にグループジョインしてから同様な構成で運用しています。

  • 開発環境がない

またEC2サーバー(Amazon Linux 1)では約5~6年以上動いていたということもあり、Ansible化はされているものの一部しかコード化をしておらず、手動でパッケージなどインストールしていたということもあり、2年前Amazon Linux 2にいざ移行しようとしても、ブラックボックスになり始め、メンテナンスがし難い状況でした。

それと分析チームがStgのサーバーにログインして開発するしか手段がないため、作業のカニバリや外部の方の動作検証ができず、非常に開発しづらい状態でした。そのため、開発環境を作ることから始めました。

※上記分析基盤の運用についてブログしていますので参考にしてみてください。

Digdag/Embulk 開発環境

ランサーズの開発環境ですが、MySQLコンテナは毎週本番データをマスクして、AWS CodebuildでECRにpushしています。開発メンバーはMySQLコンテナをpullして本番環境により近い環境で開発を行っています。それに従い、DigdagコンテナとPostgreSQLコンテナを構築して、MySQLコンテナのデータを取り込みBigQueryにシンクするような環境を作りました。また、開発環境のBigQueyシンク先でDev用のプロジェクトを作成しようと思いましたが、管理が増えることから、Stgで利用しているプロジェクトにしました。まずはDockerfileから紹介していきましょう。

  • Dockerfile
FROM openjdk:8-alpine

ENV DIGDAG_VERSION 0.9.42
ENV EMBULK_VERSION 0.9.23
ENV GOPATH /go
ENV PATH /usr/local/go/bin:/go/bin:$PATH

ENV LANG C.UTF-8
ENV APP_ROOT /opt/hoge

# Setup UTC+9
RUN apk --update add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata && \
    rm -rf /var/cache/apk/*

# install packages
RUN apk update && \
    apk upgrade && \
    apk add --update --no-cache \
    autoconf \
    bash \
    curl \
    git \
    libc6-compat \
    make \
    mysql-client \
    postgresql-client \
    py3-pip \
    python \
    supervisor \
    tzdata \
    vim

# Install awscli
RUN pip3 install --upgrade pip
RUN pip3 install awscli

RUN mkdir /root/.aws/
COPY digdag/credentials /root/.aws/credentials

# go 1.12.5 install,delete gz
RUN wget https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz --no-check-certificate -P /tmp
RUN tar -C /usr/local -xzf /tmp/go1.12.5.linux-amd64.tar.gz && rm /tmp/go1.12.5.linux-amd64.tar.gz

# google sdk command install
RUN curl -sSL https://sdk.cloud.google.com | bash
ENV PATH $PATH:/root/google-cloud-sdk/bin
RUN mkdir /root/.gcp

# install digdag
RUN curl --create-dirs -o /usr/local/bin/digdag \
    -L "https://dl.digdag.io/digdag-${DIGDAG_VERSION}" \
    && chmod +x /usr/local/bin/digdag \
# install Embulk
    && curl --create-dirs -o /usr/local/bin/embulk \
    -L "https://dl.embulk.org/embulk-latest.jar" \
    && chmod +x /usr/local/bin/embulk

# Setting Embulk version
RUN embulk selfupdate ${EMBULK_VERSION}

# Setting Digdag
RUN mkdir /etc/digdag
COPY digdag/server.properties /etc/digdag/server.properties

# setting superviser
COPY supervisor/supervisord.conf /etc/supervisord.conf
COPY supervisor/app.conf /etc/supervisor/conf.d/app.conf
RUN echo files = /etc/supervisor/conf.d/*.conf >> /etc/supervisord.conf

# Port to expose *outside* the container
EXPOSE 80

# Service to run
CMD ["/usr/bin/supervisord"]
  • supervisor/conf.d/app.conf
[supervisord]
nodaemon=true

[program:digdag]
directory=/etc/digdag
command=java -Dio.digdag.cli.launcher=selfrun -XX:+AggressiveOpts -XX:TieredStopAtLevel=1 -Xverify:none -jar /usr/local/bin/digdag server --max-task-threads 2 --config server.properties -b 0.0.0.0 --log /var/log/digdag/digdag_server.log --task-log /var/log/digdag/tasklogs --access-log /var/log/digdag/accesslogs
user=root
autostart=true
autorestart=true
stopsignal=TERM
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
  • setting.bq.sh
#!/bin/bash

aws --profile=hoge s3 cp s3://hoge/digdag/kms/bq-encrypt.key /root/.gcp/
aws --profile=hoge kms decrypt --ciphertext-blob fileb://~/.gcp/bq-encrypt.key --output text --query Plaintext | base64 -d > ~/.gcp/bq.key

gcloud auth activate-service-account \
bigquery-embulk@stg-capybara.iam.gserviceaccount.com \
--key-file ~/.gcp/bq.key \
--project stg-capybara

今回利用したイメージはopenjdk:8-alpineを選択しました。またDigdagはJavaが必須となり、元々初めはDigdag公式のイメージがないことから、1からAlpineイメージで構築を始めていると、Javaのインストールで不要なパッケージも入ってしまうことからイメージの容量が2GBにも超えてしまい、DigdagのサーバーモードからいくらWebからRunしても起動されないことがありました。なので、openjdkのイメージを利用して構築することをオススメします。それ以外のプログラムでGoも利用しており、リポジトリも分割していることからインストールするようにしています。

プロセス起動管理も同様にsupervisorで整えましたが、Stgや本番コンテナではaccess-log、digdag_server.logはCloudWatchLogsで出力されますので開発環境のみ指定しています。BigQueryで利用するサービスアカウントのJSONファイルはAWS KMSで暗号し、復号化するようにシェルスクリプト化を行いました。それ以外にEmbulkで利用しているDBのパス等は環境変数でenvファイルを利用してS3からコピーするように管理していますが、これも今後はKMSで暗号したほうがよりセキュアになるので改善していきたいと思います。

この開発環境により、digdag run、スケジューラーのテスト、bqコマンドでの操作が簡単にできるようになりました。

Stg/本番環境

  • setting-digdag-secret.sh
#!/bin/bash

## project
PROJECT1=hoge
PROJECT2=hogee
PROJECT3=hogeee
~省略~

# decrypt bq.key
aws --profile=hoge s3 cp s3://hoge/digdag/kms/bq-encrypt.key /root/.gcp/
aws --profile=hoge kms decrypt --ciphertext-blob fileb:///root/.gcp/bq-encrypt.key --output text --query Plaintext | base64 -d > /root/.gcp/bq.key

## setting local mode digdag secrets
cd /root/.gcp/ && cat /root/.gcp/bq.key |digdag secrets --local --set gcp.credential=@bq.key

## setting server mode digdag secrets
for i in ${PROJECT1} ${PROJECT2} ${PROJECT3}
do
cd /hoge/digdag/${i} && digdag push ${i} && digdag secrets --project ${i} --set gcp.credential=@/root/.gcp/bq.key
done

# delete bq.key
rm /root/.gcp/bq.key

Stg環境は上記の開発環境のDockerfileを元に構築をし、コンテナデプロイもCircleCIでマージ後にリリースされるようになっております。ちなみにDigdagではサーバーモードとローカルモードと2つあります。分析チームや開発メンバーから、今すぐこのテーブルをBigQueryにシンクしてほしい!といったことがあるため、ECS-ExecでDigdagコンテナにログインして、ローカルモードで実行できるようにしました。ここはEC2にSSHして実行するのと変わりありませんね。

digdag pushは手動で行っていましたが、どんなプロジェクトが動いているのか管理したかったというのもあったので、シェルスクリプトで自動化して、新規プロジェクトが増えた場合はそのシェルを変更してもらうようにしました。

  • ViewテーブルのクエリバックアップをECS schedule task化

[BigQuery]すべてのviewテーブルのクエリをバックアップしてみた!

BigQueryのviewテーブルのクエリは本番環境で間違えて消してもすぐ戻せるようにシェルスクリプト化をしているのですが、今まではEC2のCronからECS schedule taskに移行して、毎日0時に実行されるようにしました。上記のスクリプトは個人ブログ書いていますので参考にしてもらえばと思います。

苦労した点

  • 本番環境だけdigdagコンテナが落ちたり上がったりの繰り返しになる

Stg環境では動作等問題なかったのですが、移行時に本番環境のみコンテナが落ちて立ち上がるの繰り返しが起きました。実際にコンテナにログインしてデバッグしてみましたが、以下のエラーが出ており、調査に時間がかかってしまうということもあったので、一度DigdagのDBを作り直したところうまくいき、原因不明で移行完了してしまったというのが非常に残念でした。

org.postgresql.util.PSQLException: ERROR: relation "session_attempts_on_site_id_and_state_flags_partial_2" already exists
  • 本番移行時にエラーが多発する

embulkのプラグインでembulk-output-bigqueryを利用しているのですが、深夜のシンクで10分に1回以下のエラーが多発しました。プラグインのバージョンアップをしたところ多発することが少なくなったので、以前と同じで100以上あるテーブルのシンクに5時間で完了できるようになりました。

※以下追記

[Digdag]チューニングとFailed to add subtasks because of task limitについて

Error: OutputPlugin 'bigquery' is not found.

今後の改善と展望

  • envファイルをKMS化
  • コンテナデプロイが遅すぎる

デプロイに時間がかかると、コンテナ化のデメリットが目立ってしまいます。上記にも書きましたが、別のリポジトリで管理しているGoのプログラムがあるため、git cloneしてdep ensureをしている際にデプロイに10分以上かかってしまっているため、リポジトリをまずは一つにまとめてバイナリファイルのみを配布するように改善していこうと思います

  • DigdagコンテナのCPU使用率が高い

コンテナのCPUは2coreでメモリーが2GBなのですが、スケールアップか、Digdagのチューニングをする必要があるかと思いますが、一旦は様子見して改善していきたいと思います。たた、Fargateであるとコンテナがもし落ちてしまっても自動起動されるので運用は非常に楽になります。

  • Rubyでデータを一部加工しているプログラムの移行

ユーザー情報テーブルの各カラムなどをSQLで取得して、その日にアクセスしたユーザー一覧を取得しているプログラムがRubyで作られているのですが、これもEmbulkに移行しないとEC2サーバーを削除できないので、頑張ります…!

まとめ

コストはEC2と比べて少し割高になり、構築していて途中EC2でもいいんじゃない?と疑問を持ちましたが、Dockerによる属人化の廃止とバージョンアップのしやすさにより非常にメンテナンスしやすくなったと感じました。

ランサーズではSREチーム大募集しております。少しでも気になった方はDMください!一緒に分析基盤改善していきましょう!

EC2で運用しているWordPressサーバーをECS/Fargateに移行しました

adachin|2021年05月06日
SRE

SREチームの安達(@adachin0817)です。今回WordPressサーバーであるEC2からECS/Fargateに移行しましたが、紆余曲折を得て、苦労したところ、技術的な部分、最終的には複数のリポジトリを一つにまとめたことなどを紹介したいと思います。まずはプロジェクトとサーバーの構成から説明していきましょう。

ランサーズのWordPressとECS時代のサーバー構成

  • EC2時代のサーバー構成

  • デプロイリリースフローについて

ランサーズのWordPressは現在12個のプロジェクトがあり、リポジトリ単位で管理していました。また、AWSではEC2/Amazon Linux 2(Nginx,PHP-FPM)で運用しており、オートスケールは使用せずに二台で冗長化構成を保っていました。開発環境はDockerでできているものの、AnsibleでのNginxの設定ファイルを管理したり、自前のデプロイシステムの設定などをしなければならなく、工数が非常にかかっていました。

また、EC2でオートスケールを導入したとしても自前のデプロイシステムへの依存が発生してしまい、定期的にAMIの取り直しが必要となります。課題としてはデプロイシステムのメンテナンスも大変ですし、DockerベースとCircleCIのみに変更してデプロイサーバーを廃止することです。

ECS/Fargate + EFSのWordPressコンテナ構成

  • 各リポジトリでのデプロイ

そこで、Amazon EFSのパフォーマンスが向上したということもあり、ECS/Fargate + EFSの構成に移行しました。開発環境で利用しているDockerfileを元に、Stg環境、本番環境のWordPressコンテナのデプロイをCircleCIで行い、WordPressのソースコードはCodeBuildを利用してEFS内でgit pullを実行するように実装しました。また、自前のデプロイサーバーを廃止することができました。また、オートスケールによる負荷分散も今回導入しました。技術的な部分は以下私の個人ブログを参考にしてください!

ECS/Fargate + EFSでのデプロイをCircleCI + CodeBuildで自動化する

  • サーバーレスポンス

  • コスト

しかし、EFSだとサーバーレスポンスが平均で約3秒となってしまい、プロビジョニングドIOPのコストが高く、EC2と比べて6倍近く増加してしまいます。また、各リポジトリにCircleCIのデプロイも管理していることから、エラーが出てしまうと12個も修正をしなければなりません。CloudFrontも導入を考えましたが、一部対応していないドメインなどがあり、わざわざ複雑な構成にする必要にすべきなのかとチームと話し合った結果、この構成を諦めることにしました。以下SREチームの金澤(@yakitori009)が以前スペースマーケットさんと勉強会をした際に、LT資料を貼りますので、細かい技術的な部分は以下を参考にしてもらえばと!(ただオススメはしません。。)

【スペースマーケット×ランサーズ】初コラボイベント開催しました!

 

現在の構成とデプロイについて

  • ECS/サービス

  • CircleCIでのデプロイ

  • レイテンシー

  • リポジトリの容量
[~/www/lancers_wordpress]
adachin@ > du -sh
1.8G .

結局のところ、複数のリポジトリを一つにまとめてlancers_wordpressというリポジトリに移行しました。リポジトリ/コンテナの容量は約2GBほどでした。ここではDev、Stg、本番のDockerfileを管理し、CircleCIでのStg環境デプロイはAPIを利用しシェルスクリプトでリリース、Masterマージでの本番環境のリリースを実装しました。自前のデプロイツールでは約1分ほどでデプロイ可能ですが、ソースコードをコンテナにコピーするとなると約7~8分はかかります。しかし、コストがEC2時代とほぼ同等であること、レイテンシーも約1秒まで改善できました。

振り返って

Amazon EFSを導入するにはまだ早いと感じましたし、個人的には一つのリポジトリで全てのメディアを管理できたので分かりやすく、シンプルとなりました。今後新しいメディア等増えても工数をかけずに構築することも可能になりましたし、CodeBuidの経験もできたので結果的によかったと思います。ぜひWordPressの運用はECS/Fargateで参考にできればと思います!

SREも募集しておりますので、興味がある方はTwitterでDMいただければと思います!

Terraform v0.12.29 → v0.15.0にバージョンアップしました

adachin|2021年04月19日
SRE

SREチームの安達 (@adachin0817)です。ランサーズやグループ会社ではTerraform v0.12.29を利用して、AWSのインフラコード化をしています。ようやくグループ会社すべてAWSに移行が完了となり、次なるチャレンジとしてはTerraformのバージョンアップを対応することとなりました。また、以下4/15にv0.15.0もリリースされたということもあり、非常に良い機会となりました。

まずは各プロジェクトについて簡単に説明をしていきたいと思います!

各サービスとDeployサーバーについて

現状Terraformの管理としてはDeployサーバーの各ディレクトリごとにリポジトリがあり、terraform applyを実行して運用しています。ランサーズ本体WordPressや社内で利用しているコンテナなどはTerraformで管理されていますが、それ以外は完全にTerraform化されていない状態です。今後はterraform importですべてのリソースをコード管理していく予定なので、その際はまたブログ致します。それ以外のプロジェクトであるLancers CreativeProsheetLancers AgentMENTAはECS/Fargateに移行しているので、すべてTerraformでコード管理しています。また、CircleCIでTerraform CI(validate,plan)を実行しているということもあり、今後terraform applyもCircleCIで行う予定なので、Deployサーバーでの運用は廃止していく予定です。会社的にも成長していく中でバージョンアップが疎かになっているのは事実でした。

Terraform CIに関しては個人ブログに書いていますので参考にしてみてください。

[AWS]CircleCIでTerraformのCI/CD環境を実装してみた

バージョンアップの流れとエラー対応について

流れ

  • ブランチ作成後、DeployサーバーでTerraform v0.13.0に上げる
  • terraform init -reconfigure 
  • terraform 0.13upgrade
  • terraform planにより差分を修正する
  • tfstateファイルの修正
  • terraform planで差分がなくなったらv0.15.0にバージョンアップ
  • terraform state replace-provider registry.terraform.io/-/aws hashicorp/aws によるプロバイダー変更
  • terraform init
  • terraform planで差分がなくなっていればバージョンアップ完了

エラー対応について以下まとめてみました。まず前提として、以下のようにv0.12系からいきなりv0.15にはバージョンアップができません。なのでv0.13にあげてから徐々に差分をなくすことが重要になります。

$ terraform init -reconfigure 
│ Error: Invalid legacy provider address
│
│ This configuration or its associated state refers to the unqualified provider "aws".
│
│ You must complete the Terraform 0.13 upgrade process before upgrading to later versions.

$ terraform state replace-provider registry.terraform.io/-/aws hashicorp/aws 
Terraform will perform the following actions: 

~ Updating provider: 
  - registry.terraform.io/-/aws 
  + registry.terraform.io/hashicorp/aws 

Changing 33 resources:

また、legacy providerになっているので、providerも新しく変更しました。

  • Warning: Version constraints inside provider configuration blocks are deprecated
$ terraform init -reconfigure 
│ Warning: Version constraints inside provider configuration blocks are deprecated
│
│ on backend.tf line 5, in provider "aws":
│ 5: version = "= 3.36.0"
│
│ Terraform 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of Terraform. To silence
│ this warning, move the provider version constraint into the required_providers block.
  • backend.tf
provider "aws" {
  region = "ap-northeast-1"
  profile = "prd-menta-terraform"
}

terraform {
  required_version = ">= 0.15"
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "3.36.0"
  }
}
backend "s3" {
  bucket = "prd-menta-terraform"
  key = "terraform.tfstate"
  region = "ap-northeast-1"
  profile = "prd-menta-terraform"
  }
}

v0.13以前ではプロバイダー構成がブロック内でプロバイダーバージョンの制約がありましたが、現在は非推奨になっています。なので上記のようにプロバイダーバージョンをrequired_providersに移行しました。

  • Error: Invalid resource instance data in state
$ terraform plan
Error: Invalid resource instance data in state

on autoscale.tf line 86:
86: data "aws_iam_role" "ecs_service_autoscaling" {

Instance data.aws_iam_role.ecs_service_autoscaling data could not be decoded
from the state: unsupported attribute "assume_role_policy_document".


Error: Invalid resource instance data in state

on ecs.tf line 18:
18: resource "aws_ecs_service" "menta-app-service" {

Instance aws_ecs_service.menta-app-service data could not be decoded from the
state: unsupported attribute "placement_strategy".

0.13で廃止されたパラメーターがいくつがありますが、tfstateファイルに残っていると上記のようにエラーが起こります。対象パラメータを削除してterraform initし直せば解決します。必ずtfstateファイルはバックアップしましょう。以下不要なパラメーターの一覧を出してみました。対応後、terraform planが動作することができました。

・assume_role_policy_document
・role_id
・role_name
・adjustment_type
・placement_strategy
・cooldown
・metric_aggregation_type
・min_adjustment_magnitude
・step_adjustment
・vpc_id 
・vpc_region
・request_parameters_in_json

 

  • .terraform.lock.hclについて

https://www.terraform.io/upgrade-guides/0-14.html#opting-out-of-dependency-locking

次に、v0.15.0にバージョンアップをしてterraform initをするとterraform.lock.hclファイルというものが作成されます。これはv014.0から導入されましたが、.terraformサブディレクトリにキャッシュするロックファイルで、providerのバージョン指定がある場合に記録されます。こちらは公式がGitでバージョン管理すべきと記載されていたので対応しました。

  • 最後にCircleCIのTerraform CIを修正

これにてバージョンアップは完了となります。下記のTerraform CIでも差分がないことを確認することができました。残り3つのプロジェクトも同様にバージョンアップします。

まとめ

Terraform v0.15.0の目立った新機能はあまり見受けられませんでしたが、バージョンアップの対応方法についてご紹介させていただきました。個人的に4つのプロジェクトをバージョンアップするにはなかなか荷が重かったですが、特にトラブルなく対応できたので良かったです。v0.15.0にしたということもあって、terraform planやapplyの速度が向上したと満足しております。この機会にv0.12系の方はぜひバージョンアップをしてみてください。

またSREも募集しておりますので、興味がある方はTwitterでDMいただければと思います!

※追記 2021/08/23

MENTAをAWSに移行しました

adachin|2021年03月24日
ECS/Fargate

皆さんこんにちは。SREチームの安達(@adachin0817)です。去年10月にMENTAがランサーズにグループジョインされて、本日AWSへ移行が完了しました。この5ヶ月間どのような取り組みをしたか、改めて振り返ってみようと思います。

AWSへ移行する前

移行前の環境
https://menta.work
・さくらのクラウド
   ・PHP 7.2 / Laravel 5.5
 ・Nginx / MariaDB /Redis
 ・SendGrid
・開発環境
 ・Docker
・リリース方法
 ・SSHによるシェルスクリプト

・移行プロジェクトメンバー

元々MENTAは2018年にリリースしてからさくらのクラウドで運用していました。また、ランサーズにグループジョインする前は私が副業でサーバー保守していたということもあり、本番環境、Staging環境、Redash環境の3つで管理していましたが、デプロイがし難いことや、アクセス負荷によるレイテンシーの悪さ、冗長性や負荷分散などは担保していない状態でサービス拡大に伴い、今回AWSへ移行することになりました。他にも移行プロジェクトでは主にSREチーム @yakitori009と共に主体となり、入江さん、業務委託の方と共に進めていきました。個人的に副業で一緒に仕事していたので非常にコミュニケーションしやすかったですね。以下はAWSに移行する際にメリットやECS/Fargateによるコンテナ化することの目的をまとめてみました。

目的とコンテナにするメリット
・内部統制対応
・S3、RDSを利用したバックアップ
・CloudWatchLogs+αを利用したログの保存
・冗長化による稼働率の向上
・開発効率のアップ
・共通開発環境の構築
・リリースの属人化の排除
・GitHubと連動してリリース
・運用効率のアップ
・Immutableなサーバーの運用
・タスク単位で自由に割当のリソースが変更できる
・オートスケールによる負荷分散
・AWS WAFによるセキュリティ強化

新開発環境を作るポイント

・リポジトリ配下にdockerディレクトリを作成
・本番環境と同等の構成を再現

・ELB(H2O)コンテナ(リバースプロキシ)
・Appコンテナ
 ・PHP7.2-alpine/Laravel 5.5/Nginx
・phpMyAdminコンテナ
・MySQLコンテナ
・Redisコンテナ
・Stripeコンテナ
・Seleniumコンテナ

・SendGridコンテナ
・nodeコンテナ

・各アプリケーションの起動はSupervisorで管理
・composer installはAppコンテナでログイン時に実行
 ・supervisorctlでphp-fpmをrestartするように

開発環境は以前移行したLancers Agencyのコンテナをベースにより本番環境に近い環境を作り直しました。Docker ImageはPHP7.2-alpineを利用しており、プロセス管理はSupervisorで管理しています。メール送信ではSendGridを利用していますが、送信テスト時に実際にメールが届いてしまうことからSendGridのモックコンテナを使って開発環境でテスト送信できるように実装しました。

Staging環境/本番環境の構成

・Staging環境の構成

・本番環境の構成図

・分析基盤

・Amazon KinesisによるログストリーミングとDatadog

・Terraformで全リソースコード化
・Staging環境は極力コストを抑えタスク数は1つ
・社内用はランサーズProxyでアクセス(phpMyAdmin,SendGrid,Digdag,Redash)
・SendGridコンテナによるメールテスト
・phpMyAdminコンテナによるデータ出し
・オートスケールによる最大6台のAppコンテナで運用

・devops/SSHコンテナの利用でaws cli.MySQLの操作
※現在ではdevopsコンテナを廃止してECS Execで直接Appコンテナにログインしています
・Digdag/Embulk/BigQueryによる分析基盤の構築

・DBはRDS Auroraに移行
・ElastiCacheによるログインキャッシュ化
・画像等S3化
・ログはCloudWatch Logs
・バッチはECS Scheduled Tasks
・内部監査(ログの集約)
 ・Amazon Kinesis
・AWS KMSによる暗号化
・WAFでのDDosアタック制御
・監視はDatadogでコンテナリソースとAPM

今回インフラ面で新しく挑戦した技術ですが、ECS/Fargateでのオートスケールを実装しました。今まではタスク数2台でしたが、AppコンテナのCPUが20%超えると最大6台までスケールアウトするようになっているのでアクセス負荷時にも耐えられる環境となりました。以下私の個人ブログで紹介しているので参考にしてみてください。また、分析基盤(Digdag,Embulk,BigQuery)はランサーズでも運用しており、EC2で運用するように環境を共通にしました。BigQueryのサービスアカウントの鍵はAWS KMSを利用して暗号化し、実行時に復号しています。他にもランサーズと同様の環境であるカナリア環境で本番同等の環境も構築しました。これにより本番リリースする前にチェックができるので、リリース後でのトラブルが減ります。

https://blog.adachin.me/archives/47303

CircleCIでのリリース

・CircleCIでのコンテナデプロイ
・Staging環境/カナリア環境へのデプロイ
・github flow
 ・シェルスクリプト
 ・任意のブランチを指定してAPI経由でデプロイ
 ・Masterマージで本番環境リリース
・素早くデプロイが可能
・開発効率がアップ
・Terraform CI環境を実装

MENTAでは、AWS移行前からGitHub ActionsでUnit TestやE2E Testを行っていましたが、ランサーズではCircleCIを利用していたため移管しました。また、デプロイもCIで行うようにしました。コンテナデプロイは前回移行した方法と同様に、Terraform CI、API経由でのStaging環境デプロイ/カナリア環境デプロイ、Masterマージでの本番環境デプロイで成り立っています。

大変だったこと、今後やること

・S3 画像アップロードが一部の機能しか対応していなかった

・Laravel5.8へのバージョンアップ

・バッチや過去の企画のソースを棚卸することで、負債となっていたソースを整理できた

・Unit/Feature/E2Eの自動テスト整備
 ・PHPCSを使った規約統一
 ・PHPStan(Larastan)使った静的解析での品質向上バグ削減
 ・リファクタリング

今後やること
・Datadogでのアクセスログ可視化

・コンテナ脆弱性管理
・画像読み込みのレスポンスを改善
・Terraformバージョンアップ
・devopsコンテナを廃止してAPI経由でコンテナにログイン

上記業務委託メンバーからコメントをいただきました。AWS移行をきっかけに企画・アイディアベースのプロトタイプ開発から将来を見据え運用・保守も意識した開発スタイルに変わりました。また、技術的な負債の整理を行い移行を実施できたと思います。ランサーズと同じ開発スタイルに変わったのでやりやすくなったので良かったと感じています。

気になるコストですが、さくらのクラウド時代と比べて月額で数十万円高くなっています。しかしながら、冗長化やオートスケーリングを設定し、高い可用性を実現したサーバー構成になり、今後のMENTAを支えていく土台が整いました。

移行プロジェクトのまとめ

去年10月から始めていた移行プロジェクトですが、過去の経験が生きたこともあり、短期間で移行できたのでよかったです。協力していただいたメンバーの皆さんにも感謝いたします!個人的に副業で利用していたサービスの保守担当隣、さらにはランサーズグループへJOINし、AWSの移行を担当できたのはなかなかできないので、良い機会をいただくことができました!

お疲れ様でした!!

登壇してきました!

Lancers AgencyのサービスをECS/Fargateに移行して振り返る

adachin|2021年02月08日
AWS

SREチームの安達(@adachin0817)です。去年の5月から行っていた移行プロジェクト第二段であるグループ会社のLancers Agency株式会社の各サービスをAWS(ECS/Fargate)へ移行完了しました。今回、移行背景やECS/Fargateでのコンテナ運用について振り返りを行ってみたいと思います。

AWSへ移行する前

AWSへ移行する前はさくらのクラウドで運用していました。また今回の移行の目的としては前回のLCC移行(Rails)と同様でランサーズに統一させるということです。またECS/Fargateでの移行経験を生かして今回はCakePHPでのコンテナ運用にチャレンジすることとなりました。以下はコンテナ化にするメリットと各サービスの情報をまとめてみました。

目的とコンテナ化にするメリット
・内部統制対応
・S3、RDSを利用したバックアップ
・CloudWatchLogs+αを利用したログの保存
・冗長化による稼働率の向上
・開発効率のアップ
・共通開発環境の構築
・リリースの属人化の排除
・GitHubと連動してリリース
・運用効率のアップ
・Immutableなサーバーの運用
・タスク単位で自由に割当のリソースが変更できる
・スケールアウトのしやすさ

移行前の環境
https://prosheet.jphttps://lancersagent.com/
   ・PHP5.4 CakePHP2.5.4
 ・Apache,MySQL5.7
 ・メール送信はPostfix

・コーポレートサイト https://lancers-agency.co.jp/

 ・PHP5.5

・メディアサイト(WordPress) https://prosheet.jp/blog

 ・PHP5.5,MySQL5.7

・開発環境
 ・VagrantによるAnsibleで管理

・リリース方法
 ・Fabric

新開発環境を作るポイント

・リポジトリ配下にdockerディレクトリを作成
・本番環境と同等の構成を再現

・ELB(H2O)コンテナ(リバースプロキシ)
・Appコンテナ
 ・PHP5.5-alpine/CakePHP2.5.4/Nginx
・MySQLコンテナ
・SendGridコンテナ
・phpMyAdminコンテナ
・WordPressコンテナ(PHP7.3-alpine/Nginx)
・各アプリケーションの起動はSupervisorで管理
・composer installはAppコンテナでログイン時に実行
 ・supervisorctlでアプリをrestartするように

・開発環境のディレクトリ構成

$ tree docker/dev
docker/dev
├── README.md
├── app
│   ├── Dockerfile
│   ├── nginx
│   │   ├── local.biz.prosheet.jp.conf
│   │   ├── local.lancersagent.com.conf
│   │   ├── local.prosheet.jp.conf
│   │   └── nginx.conf
│   ├── php
│   │   ├── 15-xdebug.ini
│   │   ├── config.inc.php
│   │   ├── php-fpm.conf
│   │   ├── php.ini
│   │   ├── www.conf
│   │   └── xdebug.so
│   └── supervisor
│       ├── app.conf
│       └── supervisord.conf
├── docker-compose.noelb.yml
├── docker-compose.yml
├── elb
│   ├── Dockerfile
│   ├── h2o
│   │   ├── conf.d
│   │   │   ├── local.biz.prosheet.jp.conf
│   │   │   ├── local.lancers-agency.co.jp.conf
│   │   │   ├── local.lancersagent.com.conf
│   │   │   ├── local.pma.prosheet.jp.conf
│   │   │   ├── local.prosheet.jp.conf
│   │   │   └── local.sendgrid.prosheet.jp.conf
│   │   └── h2o.conf
│   └── service.sh
└── mysql
    ├── Dockerfile
    ├── mysql_init.sh
    ├── mysqld.cnf
    └──  service.sh

・supervisor/app.conf

[supervisord]
nodaemon=true
 
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
 
[program:php-fpm]
command=/usr/local/sbin/php-fpm -F
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
 
[program:sshd]
command=/usr/sbin/sshd -D
autostart=true
autorestart=true
stopsignal=TERM
user=root

開発環境はVagrantからDockerに移行しました。Docker ImageはPHP5.5-alpineを利用しており、Appコンテナ一つでProsheet、Lancers Agentをバーチャルホスト、プロセス管理はSupervisorで管理しています。メール送信ではPostfilxを利用していましたが、ランサーズはSendGridを利用しているため移行したいこともあり、SendGridのモックコンテナを使って開発環境でテスト送信できるように実装しました。またコーポレートサイトもCakePHPで動作していましたが、メンテナンスしやすいWordPressに移行しました。これにより素早く開発環境を提供できるようになりました。

Stg/本番環境の構成

・Amazon KinesisによるログストリーミングとDatadog

・stg dockerディレクトリ構成

$ tree docker/stg
docker/stg
├── deploy.sh
└── prosheet-app
    ├── Dockerfile
    ├── README.md
    ├── common
    │   └── prompt.sh
    ├── nginx
    │   ├── default.conf
    │   ├── stg.biz.prosheet.jp.conf
    │   ├── stg.lancersagent.com.conf
    │   ├── stg.prosheet.jp.conf
    │   ├── stg.try-out.work.conf
    │   └── nginx.conf
    ├── php
    │   ├── php-fpm.conf
    │   ├── php.ini
    │   └── www.conf
    └── supervisor
        ├── app.conf
        └── supervisord.conf
・Terraformで全リソースコード化
・Stg環境は極力コストを抑えタスク数は1つ
・devops/SSHコンテナの利用でaws cli.MySQLの操作
・DBはRDS Auroraに移行
・メール送信はPostfixからSendGrid化
・コーポレートサイトはWordPress化
・ALBのリスナールールによるパスによる転送
・画像等S3化
・WordPressの画像
 ・WP Offload Media LiteプラグインでS3に
・ログはCloudWatch Logs
・バッチはECS Scheduled Tasks
・内部監査(ログの集約)
 ・Amazon Kinesis
・デプロイはCircleCIのみで実装
・監視はDatadogでコンテナリソースとAPM

前回のLCC移行と構成は同様ですが、一番大変だったのはSendGridへ移行、WordPressコンテナ化に伴う画像をS3に移行、職務経歴書などのファイルをS3に移行することでした。もちろんコンテナの場合ソースコードで管理していないファイルや画像等はデプロイ時に削除されてしまうので、これらの実装ができていないと移行ができません。主にアプリエンジニアが対応していただき、SendGrid移行時ではメール送信ロジックが一つにまとまっていなかったので対応箇所が多かったところと、SendGrid APIの仕様で送信結果を別途取得しなければならないのが想定外でした。S3移行ではファイルアップロード、ダウンロード処理が共通化されている部分と共通化されてない部分があり、対応方法もそれぞれ異なり、昔のソースコードへの対応後、確認方法がわからなくて苦戦しました。

また、さくらのクラウドからAWSに移行してコストがどのくらい変わったのか確認してみたところ、さくらのスペックが高かったこともあり、年間約60万ほど削減できました。続いてはCircleCIでのコンテナデプロイについて紹介します。

CircleCIでのコンテナデプロイ

・Stg環境のデプロイ
 ・github flow
 ・シェルスクリプト
  
  ・任意のブランチを指定してAPI経由でデプロイ
・素早くデプロイが可能
 ・開発効率がアップ
・Masterマージで本番環境リリース
・Terraform CI環境を実装

$ sh deploy.sh 
下記のようにブランチを指定して実行してください。 
(例) sh deploy.sh ブランチ名

$ sh deploy.sh
  
  下記のようにブランチを指定して実行してください。
  (例) sh deploy.sh ブランチ名
  
$ sh deploy.sh fix-nginx
{
  "number" : 74,
  "state" : "pending",
  "id" : "xxxxxx-xxxxx-xxxxx-xxx-xxxxxxxx",
  "created_at" : "2020-08-27T06:14:43.973Z"
}
ブランチ名/ fix-nginx のStgデプロイを開始しました!

Stg、本番環境デプロイはCircleCI一つで実装しています。StgではCircleCIのAPIをシェルスクリプトで引数(ブランチ名)を指定してデプロイし、本番はMasterマージ後にデプロイするように実装しました。WordPressもリポジトリ化をしてデプロイも同様になります。CIではTerraformが動作していますが、今後PHPのCIも取り入れる予定です。

移行プロジェクトの振り返りとまとめ

今回はランサーズSREチーム、アプリメンバーと共に移行していきましたが、前回の経験もあったことで素早く構築することできましたし、PHPでの本番コンテナ運用もノウハウが高まったので非常にいい経験となりました。また、個人的な感想としてPROsheetは5年前のシェアゼロ時代から知っていたので、前CTOにも移行したことを報告して、メンテナンスされてありがたいとのことで感謝もされて非常に感慨深いです。

今後やることとしては現在PHPは5.5、CakePHPは2.5.4を利用しています。先日PHP8もリリースしたということもあって、SREチームとアプリメンバー含めてバージョンアップの対応やコンテナの脆弱性検知(Trivy)やDatadogでのアクセスログ可視化を行っていきたいと考えています。半年間近くでしたが、レガシーシステムを移行するにあたって時間もかかりましたし、最後までやりきることができたので非常に感動です。次の移行プロジェクトはMENTAとなりますので、また移行したらブログ書いていこうと思います。ありがとうございました!

ランサーズの各サービスをECS/Fargateへ移行する取り組みについて

adachin|2020年12月07日
SRE

ランサーズ Advent Calendar 2020 7日目の記事でございます。

皆様お久しぶりでございます。SREチームの@adachin0817です。タイトルの通り、SREチームではランサーズ内の各サービスをECS/Fargateへ移行する取り組みを行っています。その中でコンテナにしてよかったこと、課題等含めて振り返ってみたいと思います。

2020/4〜

去年にシクロマーケティング社がランサーズのグループにジョインされました。現Lancers Creativeという月額定額制のクリエイティブサービスをAWSに移行することになり、初の試みであるECS/Fargateにチャレンジすることになりました。新技術への挑戦に対してかなり時間をかけてしまいましたが、この経験を元に他のグループ会社のインフラやランサーズをコンテナへ移行する計画を整うことができました。次に今年4月に移行してから半年ほど経ちましたが、移行して良かったことや、課題をまとめていきたいと思います。

・開発メンバーだけでRuby/Railsを最新にバージョンアップができた
EC2と比較すると、ランサーズではAnsibleを運用しており、我々SREチームへの工数がなんとしてもかかってしまいます。逆に開発メンバーにAnsibleを修正してもらうというのも学習コストもかかってしまいます。コンテナの場合はDockerfileで完結し、テスト等は開発環境やStg環境へCircleCIでデプロイすればいいので、バージョンアップ対応まで1ヶ月で対応することができました。基本的にはインフラやアプリ面、全て開発チームに任せることができ、インフラでのちょっとした相談で済むようになりました。これは凄い…

・本番環境にSSHでログインすることがない

Fargateというマネージドサービスにしたこともあり、ログの確認はすべてCloudWatch Logsで確認することができました。またログの転送はAmazon KinesisでリアルタイムでS3にシンクしています。なので調査するために不要なSSHのportを開ける必要がないので、セキュアな環境が実現できたと思います。

・コンテナ台数を増やすためにはタスク起動数を変更するだけで良い

こちらもEC2と比較すると、AMIを取得、もしくはAnsibleで構築してアプリをデプロイするまでの工数がかかりますが、コンテナの場合はサービスのタスク起動数をTerraformで変更するだけなので、あっという間に構築が終わります。

・開発環境が重い

Docker for MacのファイルシステムがLinuxと違うため、ページ遷移が遅いという課題があります。ここらへんはdocker syncの検証や不要なファイルやディレクトリはmountしないなど改善していこうと思いましたが、以下の記事によりgRPC FUSE をOFFにすることで、ページ遷移が5秒から1秒まで高速化しました。開発環境が遅いことがあれば試してみてください。

https://blog.hanhans.net/2020/11/28/docker-for-mac-slow-again/

・コンテナデプロイまでに10分以上かかる

CircleCIでのコンテナデプロイでネックなのはbundle installに時間がかかってしまうことです。ここはマージ後に走らせるのではなく、commitしてpushするタイミングでコンテナデプロイするのが時間削減になるので修正していこうと思います。

・コンテナでのオートスケールを経験していない

まだまだ小さいサービスということもあり、負荷対策としてコンテナでのオートスケールを経験したことがないので、ランサーズ本家移行の時にテストをする方向でいきたいと思います。

あとはTryでやっていきましょう!以下技術的な内容はブログ書いているので参考にしてください。

グループ会社のインフラをECS/Fargateに移行して振り返る

2020/5~

https://prosheet.jp/

https://lancersagent.com/

一段落して、5月からはグループ会社である「Lancers Agency株式会社」のサービスをECS/Fargate移行計画が始まりました。このサービスはCakePHPで動いているので、ランサーズに近い環境となっております。ようやくPostifixからSendgridに移行、アップロード系の処理をS3へ移行が完了したところで、あとはStg環境で一通りテストができれば今月12月には移行できる予定です。コーポレートサイトはWordPressで動いており、こちらはコンテナに既に移行済みとなっています。移行後にやることは以下になります。

・PHP/CakePHPのバージョンアップ

現在PHPは5.5、CakePHPは2.5.4を利用しています。先月PHP8もリリースしたということもあって、SREチームと開発メンバー含めて対応していきたいと考えています。

また、9月にアソビューさん、コネヒトさんで合同LT大会も行ったのでコンテナ活用について登壇させていただきました。技術的な部分は以下参考にできればと思います。

【アソビュー×ランサーズ】AWSでのオーケストレーションツールの活用事例(EKS vs ECS)を開催しました!

【コネヒト×ランサーズ】コネヒトマルシェ「事業を支えるWeb開発」vol.2を開催しました。


2020/10~現在

10月からはMENTAがグループジョインされました。個人なお話になるのですが、副業でMENTAを使っているということもあり、一緒に仕事ができる!ということもあって、なかなか感慨深いところもありました。また、業務委託でMENTAのサーバーを全て管理していたということもあり、アクセス数も伸びているので、こちらもさくらのクラウドからECS/Fargateに移行することになりました。現状のインフラ移行について進捗を共有しましょう。

https://signal.diamond.jp/articles/-/325

・開発環境

開発環境はランサーズと同様の環境にするため、作り直しからプロジェクトが始まりました。今回は機能ごとに8つのコンテナで実装することにしました。AppコンテナはAlpine Linuxで構築しています。LancersAgencyの開発環境を参考にしたということもあり、1日で実装することができました。MENTAではPHP7.2、Laravel5.4を利用していますが、今後バージョンアップをする予定です。

・Stg環境

・本番環境

Stg環境は今までの構成とは少し違って、Redashやランサーズと同様に分析基盤/Embulk,Digdag,BigQuery(BI)を運用したいとのことで、コストダウンのためEC2で構築することにしました。それ以外はLancersAgencyと環境は同等です。現在は一部S3への画像アップロードが対応していないため、コンテナ内に画像がアップロードされてしまい、デプロイ時に消えるという現象が発生しています。こちらも早急に対応しているところです。Stg環境とは別に、本番のデータも見れるようにテスト環境を作りたいということで、もう一つAppコンテナを用意することになりました。こちらはランサーズと同じ運用でPrivate環境となります。

・デプロイ

CIは現在GitHub Actionsを利用しているので、CircleCIに寄せようとしています。それ以外にもTerraform CIも動いているので、。コンテナデプロイはAPI経由でStg、Privateにリリース、Masterマージで本番リリースするように実装しています。Slackでもリリースを実装中です。

MENTAは移行後に詳しく技術的な部分を書きますのでまたの機会に!

ランサーズ本家の移行は?

・現状の構成

CakePHP2→CakePHP4へのジャンプアップ

現在ランサーズ本家はEC2でオートスケールを利用して運用しているので、まずはAmazon Linux 1から2に移行を取り組んでいます。またSRE/QAの金澤さん、QAのまみーさんが主体となって、CakePHP4にバージョンアップを行っています。その後管理画面をCake4とPHP8にしてからECS/Fargateに移行し、本体のバージョンアップが完了後、実施となる見込みになっています。なので長い時間をかけて取り組むという認識でございます。

まとめ

直近の課題としてはまずは今月中にLancersAgencyの各サービスを移行することと、MENTAの移行をやりきることですね!そしてSREチームが今どんな課題に向けて取り組んでいるのか雰囲気が伝わったかと思います。もちろんコンテナ移行以外にも改善するところはたくさんあります。なので….

SREが足りません!

気になる方はぜひ応募待ってます!
明日はつっちーさんです!

グループ会社のインフラをECS/Fargateに移行して振り返る

adachin|2020年05月11日
AWS

皆さん元気ですか!?SREチームの@adachin0817です。去年から行っていた移行プロジェクトで、グループ会社である、シクロマーケティング株式会社の「ミギウデ」をさくらVPSからAWSへ移行しました。今回、移行背景やECS/Fargateでのコンテナ運用について簡単にご紹介と振り返りを行ってみたいと思います。

なぜAWSへ移行するのか

AWSへ移行すると冗長性の担保などが挙げられますが、一番は開発環境やインフラなど、すべてランサーズに統一させるということが第一の目的です。それに伴い、ミギウデ自体のサービスがシンプルなインフラ構成ということもあり、インフラ運用の手間をなくしたいということから、ECS/Fargateで初の外部サービスとしてコンテナ運用にチャレンジしてみようとなりました。

目的とコンテナ化にするメリット

・内部統制対応
・S3、RDSを利用したバックアップ
・CloudWatchLogs+αを利用したログの保存
・冗長化による稼働率の向上
・開発効率のアップ
・共通開発環境の構築
・リリースの属人化の排除
・GitHubと連動してリリース(人件費削減)
・運用効率のアップ
・Immutableなサーバーの運用
・タスク単位で自由に割当のリソースが変更できる
・スケールアウトのしやすさ

まずは開発環境から紹介していきましょう。

開発環境

・構成

$ tree dev
dev
├── README.md
├── cyclo-app
│    ├── Dockerfile
│    ├── gemrc
│    ├── nginx
│    │   ├── app.conf
│    │   └── nginx.conf
│    └── supervisor
│         ├── app.conf
│         └── delayed_job.sh
├── docker-compose.yml
├── elb
│   ├── Dockerfile
│   ├── h2o
│   │   ├── conf.d
│   │   └── dev.cyclo-creativecloud.com.conf
│   │   └── h2o.conf
│   └── service.sh
└── mysql
    ├── Dockerfile
    ├── mysql_init.sh
    ├── mysqld.cnf
    └── service.sh

・docker-compose.yml

version: '2'

services:
  elb:
    build: ./elb
    image: cyclo-elb
    hostname: elb
    networks:
      lancers:
        ipv4_address: xx.xxx.xx.xx
    extra_hosts:
      - "dev.cyclo-creativecloud.com:xx.xxx.xx.xx"
    container_name: cyclo-elb-xx-xx
    ports:
      - 80:80
      - 443:443
  app:
    build: ./cyclo-app
    image: cyclo-app
    hostname: cyclo-app
    networks:
      lancers:
        ipv4_address: xx.xxx.xx.xx
    extra_hosts:
      - "dev.cyclo-creativecloud.com:xx.xxx.xx.xx"
    container_name: cyclo-app-xx-xx
    volumes:
      - ~/www/app:/var/www/app/
  mysql:
    build: ./mysql
    image: cyclo-mysql
    hostname: mysql
    networks:
      lancers:
      ipv4_address: xx.xxx.xx.xx
    container_name: cyclo-mysql-xx-xx
    ports:
      - 3306:3306

networks:
  lancers:
  external: true

・ランサーズ開発環境

開発環境ですが、リバースプロキシとして使うELB(H2O)コンテナ、App(Rails)、MySQLコンテナの3つで動作しています。これにより、AWSやランサーズの開発環境(上記図)と同じ構成に仕上がることができました。

このサービスはRailsでできているということもあり、bundle install はコンテナにログインしてから実行しています。また各アプリケーションの起動はSupervisorで管理しました。また、Dockerはプロセスがフォアグラウンドで動いてないと、コンテナが終了するため、フォアグラウンドで実行するように注意しましょう。

・cyclo-app/supervisor/app.conf


[supervisord]
nodaemon=true

[program:app]
command=bundle exec unicorn_rails -c config/dev_unicorn.rb -E development
autostart=true
autorestart=true
stopsignal=TERM
user=root
directory=/var/www/app/
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:delayed_job]
command=sh /etc/supervisor/conf.d/delayed_job.sh
autostart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stopsignal=TERM
user=root
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:sshd]
command=/usr/sbin/sshd -D
autostart=true
autorestart=true
stopsignal=TERM
user=root

Stg/本番環境

・Stg

・本番環境

・Docker(Stg/Prd)

$ tree
├── prd
│   └── cyclo-app
│       ├── Dockerfile
│       ├── README.md
│       ├── common
│       │   └── prompt.sh
│       ├── gemrc
│       ├── nginx
│       │   ├── app.conf
│       │   └── nginx.conf
│       └── supervisor
│           ├── app.conf
│           └── delayed_job.sh
└── stg
    └── cyclo-app
        ├── Dockerfile
        ├── README.md
        ├── common
        │   └── prompt.sh
        ├── gemrc
        ├── nginx
        │   ├── app.conf
        │   └── nginx.conf
        └── supervisor
            ├── app.conf
            └── delayed_job.sh

Stgと本番環境はすべてTerraformでインフラコード化をしています。これによりStg環境で一度作ってしまえば、本番環境構築に工数がかかることなく、設定変更時のミスが減ります。RDSAuroraを使い、コンテナのログはすべて標準出力をしてCloudWatch logに出力しています。各ログは今までLambdaを利用してS3に保存していましたが、Pythonのバージョンアップ対応でメンテをしたくないことから、今回はAmazon Kinesisを利用しました。他にもAppコンテナ以外にコマンドでRDSへ接続、aws cliの利用や開発者がrails consoleを使いたいため、Appコンテナとまったく同じコンテナであるdevopsコンテナを作成しました。このコンテナはランサーズの踏み台サーバーからSSHでログインできるようにVPC Peeringしています(下記図より)。SSMのセッションマネージャーもありますが、手軽に踏み台からSSHしたいということから、今回はこのような構成にしました。ちなみにECSのタスク定義ですが、AppコンテナはSupervisorで起動し、devopsコンテナはフォアグラウンドでSSHが起動するように制御しています。バッチは元々Railsのwheneverを入れて、schedule.rbファイルで管理していましたが、 ECS Scheduled Tasksを使って実装しました。監視はDatadogを使って、コンテナ内のCPU,メモリーなどのリソースと外形監視を行っています。

・VPC Peering

・コンテナデプロイの流れ

コンテナのデプロイはCircleCIで実装しました。GitHubdevelopブランチにマージするとStgが反映され、masterブランチにマージすると本番が反映されます。CIrspecCDCircleCIのOrbsを利用しており、Orbsがなかった頃はecs-deployを使ってシェルスクリプトを使っていましたが、簡単にDockerfileのbuild、ECRへpushECSリビジョンの更新、サービスタスク定義の更新、DBマイグレーションまで可能になりました。今までCapistranoによってサービスのリリースを行っていましたが、CircleCIによってリリースフローを確立できました。

移行プロジェクトと振り返り

今回3社合同で行われた移行プロジェクトですが、SREチームでコンテナの設計、デプロイなどを実装し、チームの皆様にフォローして頂きつつ、開発チームと連携してサーバーサイドの動きなどをヒアリングしながら進めていきました。また、リモートワークが当たり前になってきた頃には、とにかく通話してコミュニケーションを取るように心がけました。最後にチームで良かったところ、悪かったところを振り返ってみます。

Keep

・モダン技術へのチャレンジをやりきれた
・初めて外部サービスとしてコンテナ運用を行うことができた
・RailsでのECS/Fargateでの知見が高まった
・メンテナンス時にトラブルなく移行できた
・CircleCIのOrbsを使ったCI/CDの知見
・今後他サービスでのCI/CDの検証となった
・Datadogをサイドカーコンテナとして動かした際の知見
・コンテナの監視検証となった
・バッチ置き換えの検証となった
・内部統制対応

・ログの取得
・冗長性の確保
・データベースのバックアップが取れている
・リリースフローを確率できた

Problem

・当初の移行スケジュールが伸びてしまった
・開発環境が不十分なできになっていた
・コンテナの設計とやり直しに時間かかりすぎていた
・突発的なエラーにも時間かかりすぎていた
・デプロイの実装に時間かかりすぎていた

Try

・ECSの他サービスへの適用
・移行スケジュールとタスクを最初から明確にする

まとめ

今回外部サービスとしてコンテナ運用は初めてのチャレンジということもあり、やりきれて非常にいい経験となりました!今後はこの知見を活かしてランサーズもFargate化を計画をしていますので、その時はまたブログします。

※余談
今回の移行プロジェクトで会社で表彰頂きました!ありがとうございました!!

Lancers x ROBOT PAYMENT x LAPRASでLT交流会をしてきました!

adachin|2020年02月18日
イベント/登壇

SREチームの@adachin0817です。今回はROBOT PAYMENTさん、LAPRASさんの三社合同でLT交流会を行いました。人数も約20名ほどで、かなりの盛り上がりを見受けられ、ランサーズでは私含めて3人がLTをしました。

ちなみにロボペイさんもラプラスさんも弊社はユーザーとして使わさせて頂いております!ありがとうございます!!ではイベントレポートをいきましょう!

LAPRAS社 19:00~

LAPRAS本社にて行いましたがピザとお酒が用意されていました!ありがたや・・!後ほど串カツ田中もデリバリーで来たのでなかなかお腹が満たされました。イベントでは串カツ田中のデリバリーおすすめです!!オフィスには集中ルームやスタンディングデスクなど、エンジニアには最適の環境が用意されていると感じました。(写真撮るの忘れた)羨ましい!!!

タイムスケジュール

3社合わせて9人で、持ち時間は一人5分でした。過ぎてしまうと強制終了してしまうので、ここは腕の見せどころですね。ここから一人ずつ紹介していきます。

LAPRASのスコアリングロジックについて @showwin

LAPRASのスコア機能があるのですが、そのロジックをCTOの伊藤さんに説明していただきました。機械学習というよりもルールベースで実現しているとのことでした。結構大変そう。

Do u know DigitalOcean? @adachin0817

https://blog.adachin.me/archives/11245

個人のブログサーバーの裏側(DigitalOcean)についてLTさせていただきました。5分で話せなかったことも多かったので自分のブログに書きましたのでぜひ!

今から始める楽しいスマートホーム

今から始める楽しい スマートホーム ←リンクより

ロボペイの高木さんに自宅でのIoTスマートホームを実現したお話で非常に実用的なお話でした。私個人ではAlexaしか使っていなく放置気味なので、電気とかカーテンも自動化しよう!!

フロントエンド設計の問題点と解決策について @nasum360

LAPRASでのフロントはVue.js、vuex、vue-routerでLAPRAS SCOUTはTypeScriptに移行中とのことでした。現状としてはVue.jsとTypeScriptの相性が悪く、テストやパフォーマンスを意識できていないことから、今後はストア設計の見直しと共通コンポーネントの切り出しをしてパフォーマンスの計測をしていくとのことでした。テスト書きましょう!

オプティマイザトレースで運用改善に愛を @mamy1326

https://speakerdeck.com/mamy1326/love-operation-with-optimizer-trace

1月にランサーズにジョインしてくれたまみーさんです。ぺちぱーで、DBにも鬼強なので日々私は学びしかないのですが、今回はオプティマイザトレースで愛をもって改善していこうというお話でございました。スライドが74枚という驚異的な量でしたが、すば抜けたトークで会場を盛り上げ、DBでの運用改善は実行結果を必ずチェックし、疑問はトレース、常に計測して監視することが重要です。そこから見直しをしていき、統計情報を調べて更新して愛を持って改善していきましょう!!

SREが自宅のリフォームをしたお話 @j_untanaka

ロボペイのSRE 田中さんでは自宅のリフォームをしたときに「POおよびPMは自分である」という名言に会場は盛り上がりました。結局はIT以外でのプロジェクトがあり、何を作りたいかは発注者が考える必要があるので、ソフトウェア開発とほぼ同じでした。確かに!!!

API GatewayとLambdaとGolangで作るサムネイル生成システム @yakitori009

ランサーズ SRE 金澤さんですが、元々ランサーズのサムネイル生成処理はAppサーバー内でImageMagickを起動して行っていました。しかし安定性の問題などで、Lambdaに置き換えたというお話です。5分で終わらず、ちょうどJAWSでも話すということもあって、続きは現地でお会いしましょう!

ローラー開発の現実とつらみ @Chan_moro

LAPRASのクローラーエンジニア両角さんでは実際にランサーズをリアルタイムクローリングをするということで、私はWAF設定しているのであまりやらないようにとツッコミを指摘しましたが笑 Pythonで簡単に可能であること。また、ランサーズのサイトはキレイなのでクローリングしやすいとのことでした!おもしろい!

俺たちのパフォーマンス計測 @trunkatree

最後はロボペイ SREの遠藤さんです。監視はMackerelとNewRelicを使い、レスポンスタイムのグラフがあっているのかバラつきがあるため、よりユーザーに寄り添った計測がしたいということでNewRelicのAPIからメトリックスを取って、RDSに保存してRedashで可視化しているそうです。インフラ系の可視化良き!!

まとめ

合同LT大会ありがとうございました!懇親会では各社エンジニアさんとの技術交流もでき、アットホームな雰囲気で非常に楽しむことができました。ちなみにLAPRAS社では個人でイベントするのにオフィス使ってもいいとのことです!ぜひこのブログを見て、ランサーズと一緒に合同LT大会やりましょう!ではまた!

二次会はワイン!

ランサーズのSREがリモートワークで気をつけていること

adachin|2019年12月24日
SRE

ランサーズ Advent Calendar 2019 24日目の記事となります。
SREチームの@adachin0817です。

今年のクリスマスは平日ということで、先週の土日にパーティーを開催した方も多いのではないでしょうか。ちなみに私はディズニーランドへ15年ぶりに行ってきました。クリスマス仕様になっており、アトラクションでキャラクターたちがサンタコスになっていたり、大規模なイルミネーションの凄さに感銘を受けました。(ほんと凄い!!)

さてさて、ランサーズは働き方の中でリモートワークが可能となっています。10月頃に週1~2で試してみたので、その時に業務で気をつけていることをご紹介したいと思います。

We Are Lancers / 44のスライドで知るランサーズと勤怠報告

https://speakerdeck.com/lancers_pr/44falsesuraidodezhi-ruransazu?slide=38

ランサーズのエンジニアは基本「裁量労働制」なので、成果が出ていれば何時に出社してもいいですし、早く帰ることも可能です。勤怠は10時出社に遅れそうな場合、 slackに必ず報告をしています。裁量労働制と言っても「あの人がいない!」「え、仕事頼みたいけど何時に来るの!?」とならないように事前に報告するのは当たり前ですね。

リモートワークに関わらず分報でやることを報告

リモートワークで欠けてしまうのは「チーム内での雑談」だと思います。現在SREチームは3人で取り組んでいますが、質のいい雑談が多いので、「こういうのやりたいよね〜」「ここらへん課題かもね〜」「いやあだちんそこは気をつけようよ!」などのコミュニケーションが頻繁にあります。また、チャットでの質問をする際に発言しにくいことから業務にハマって進まないということも度々あります。そこらへんのフォローを含めてコミュニケーションを取るよう、分報での報告がベストかと感じています。

分報では出社したら毎日 今日やること を中心に箇条書きで見える化していきます。タスクが終わったら以下のように スタンプをつけるとチーム内でも共有されるのでちゃんとアウトプットしているか判断できます。出社するときには口頭での報告が早いので分報には毎回書きませんが、リモートワーク時は以下のように、めいいっぱい つぶやくことを意識し、気楽なコミュニケーションでも相手のことを考えながらチャットするように心がけています。

次は実際にリモートワークを通して感じたことをまとめてみました。

リモートワークをやってみて気づいたこと

メリット

・集中力が圧倒的に上がる
・ランチが自炊(思わず食べすぎてしまう)

・満員電車との戦いがない(通勤、帰宅がない)
・稼働率が上がる(朝起きてすぐデスクで始められる)
・休憩時間に家事ができる
・ちゃんとした夕飯が作れる

会社での集中力とは違い、いつもとは違った能力が芽生え、ハマっていた技術が急に解決してしまうことに驚きました。また、ランチが自炊ということもあって、お金がかからず食べれることも。他にも満員電車に遭遇しないので、体力を使わずに業務ができるのはいいですね。あとは家事や夕飯もちゃんと作れるので嫁に感謝されます。

デメリット

・1日の歩数が約200歩で運動不足
・チームとの距離感を感じる(出遅れてる感)
・返事/レビューをすぐもらえない
・チャットでの温度感が認識しづらい(気を遣ってしまう)
・成長してるのか不安
・通勤時間がないので読書がなくなる

個人の見解ですが、久しぶりに会社へ向かうと相当運動不足で、坂や階段で息切れになってしまったということがありました。対策としては15分くらい外で散歩するのが良いでしょう。スタバ行ってコーヒーを買うという習慣づけをするとかですね。やはり、リモートワークの場合チームで仕事をすると出遅れてる感や、温度感などが明確に伝わらないので、自らコミュニケーションを取り、アクションを起こすことが大事ですね。一人でもくもくと開発をするには最高の働き方かと思います。

まとめ

リモートワークはメリット、デメリットがある中で、一人ひとりが何かしらルールを持って仕事をすることが大事かと思います。リモートでも出社しても個人のスキルをフルパワーに発揮できるように業務をしていきましょう!

以前弊社の社員である市川さん(@EikoIchikawa)が海外でフルリモートワークを経験した記事があるので、ぜひご覧になってみてください!

https://amp.review/2019/09/29/sp-lancers/

[digdag]BigQueryのデータをシェルスクリプトでDBにインポートする方法

adachin|2019年12月13日
SRE

ランサーズ Advent Calendar 2019 13日目の記事となります。

SREチームの@adachin0817です。3日目の記事見てくれましたでしょうか!?(以下より)

https://engineer.blog.lancers.jp/2019/12/lancers-tech-night/

今回はBigQueryのデータをシェルスクリプトでDBにインポートする方法をブログしたいと思います。この裏で動いているのがご好評頂いているランサーズの機能であるユーザーレポートのプロフィール閲覧数のデータを表示しています。ユーザーレポートに関しては以下を参考にしてみてください!

https://info.lancers.jp/20847

 

今までの運用と問題点

去年のアドベントカレンダーでもご紹介しましたが、上記のプロフィール閲覧数は深夜にdigdag/embulk(embulk-input-bigquery)でアプリケーションログをviewとして作成し、BigQueryのviewテーブルからRDSへシンクしています。去年ベータ版をリリースし、運用してきましたが、いくつかデータが取れないことがあり、取れる日に一気にdigdagで再シンクをしていました。さすがに手動で対応するには厳しいので、改善案をいくつか上げてみました。

改善案その1

  • embulk-input-bigqueryプラグインのバージョンアップ

このプラグインをバージョンアップすれば値が取れたり取れなかったりという事象がなくなるのではないのか、とチームで話し合いあったので取り組んだところ、バッチも動いており、Rubyやembulkのバージョンアップをしなければなく、各プロジェクトが問題なく動作するのかということもあり、断念しました。

改善案その2

・BigQueryのデータをdigdagサーバーでcsvにエキスポートし、RDSへインポートする

https://cloud.google.com/bigquery/docs/reference/bq-cli-reference?hl=ja

bqコマンドには様々なフラグ(オプション)が対応しているので今回「改善案その2」を実装してみました。これであればdigdagでシェルスクリプトを実行できるので、代用案として最適かと思われます。

bigquery-to-mysql.sh

#!/bin/bash

$(".common/secrets.sh")
YESTERDAY=$(date +%Y-%m-%d -d '1 days ago')

## views out csv
/home/hoge/google-cloud-sdk/bin/bq query --use_legacy_sql=false --format=csv --max_rows=100000 "SELECT * FROM \`prd-hoge.views.views\` where dt=\"${YESTERDAY}\"" > /tmp/views.csv

## add ,
sed -i '1,2d' /tmp/views.csv
sed -i "s/^/,/g" /tmp/views.csv

# restore
mysql -h "${HOST}" -u hoge -p"${PWD}" -P "${PORT}" hoge -N -e "LOAD DATA LOCAL INFILE '/tmp/views.csv' INTO TABLE views FIELDS TERMINATED BY ','"

## rm profile_views.
rm -f /tmp/views.csv

1. bqコマンドで対象のテーブル(1日前)を/tmpにcsvで吐き出す

--max_rows のデフォルトは100なので100レコードしか取り出せません。明示的に10万レコードを指定しております。

2. sedで2行分削除と置換

3 .DBにリストアし、csvを削除

次はdigdagで動かしてみます。

run.dig

timezone: Asia/Tokyo

schedule:
  daily>: xx:xx:xx

+views:
  !include : 'retry.dig'
  call>: views.dig

retry.dig

_retry:
  limit: 5
  interval: 60
  interval_type: exponential

views.dig

_error:
  sh>: /digdag/post_slack.sh "[${session_time}][${session_id}] DigDag Fail in Bigquery to MySQL views"

+load:
  >: /digdag/bigquery_to_mysql/embulk/bigquery-to-mysql.sh

retry.digを入れることでシェルスクリプトがエラーを起こしても5回再実行されるように設定しています。(以下参考に)

https://blog.adachin.me/archives/10835

 確認

今の所、毎日漏れなくプロフィール閲覧数が表示されるようになりました!

まとめ

今回初めてbqコマンドを使ってシェルスクリプトにしてみましたが、実際に書いてみると結構シンプルでした。皆さんもぜひ参考にしてみてください!

Lancers Tech Nightの取り組みとは!?

adachin|2019年12月03日
イベント/登壇

ランサーズ Advent Calendar 2019 3日目の記事となります。

SREチームの@adachin0817です。最近はTerraformやったりAWS Fargateに挑戦してます。あっという間に2019年も終わりですね。皆様は今年の目標など達成できましたでしょうか!私はリバウンドしてしまったので来年は気合い入れて別人になろうと思います。さてさて今回は「Lancers Tech Night」という社内エンジニアイベントついてご紹介できていなかったので、雰囲気や取り組みについてブログできればと思います。

Lancers Tech Nightとは

ランサーズをテックカンパニーにしたいため、お昼は「Lunchers(ランチャーズ)」という外部のエンジニアさんをお呼びしてLTしながらランチを食べるというイベントを定期的に行っています。(以下アーカイブを参考に)

https://engineer.blog.lancers.jp/category/event/lunchers/

また、「Lancers Tech Night(ランサーズ テック ナイト)」では不定期で社内のエンジニアをランダムで選び、金曜日にLT大会を実施しています。目的としては以下の3つになります。

  • ランサーズをテックカンパニーにするため
  • エンジニア同士のコミュニケーション活性化
  • 技術のシェアにより技術力アップ

前回の様子(2019/07/12)

技術的なお話から、マネジメントまで内容は幅広く、普段業務では見れない姿を見ることができました。
そして先月行われたLancers Tech Night #2についてレポートもしてきましたので以下ご覧になってみてください!
※一部資料は公開していないものもありますのでご了承ください


Lancers Tech Night #2

reactとreduxを使った簡単なアプリ作りました
@Kakki0310

構成について紹介
・TypeScript
・jsしか使ってない
・mainのコンポーネントは管理画面全体の部分
・reduxまで表示させる
・実際にpostを作るには
・formをreactの形式を使っている
・投稿ボタンと2つのフォームがある
・post.jsでそれぞれの関数を定義して、どういうアクションが行われたのかを組み込める
・delete postは投稿を削除している。
・この形はランサーズでも使われている
・reactはCakePHPのctpファイルの表側のようなイメージ
・reduxは裏側の処理をしているイメージ

増税対応開発の教訓
@吉本くん

技術よりも個人の反省点
・日付が考慮されていない
・税率はconfig設定
・増税対応って?
・税を扱うクラスを作成
・日付を元に税率を算出
・税率、日付はprivate
・これにより呼び出し元の全差し替えができる

起こったこと
・対応漏れによる不具合
・振込手数料のタイミング、表示上の金額ずれ
・仕様による不具合など

原因
・ロジックの分散
・規模が大きい
・手数料の計算メソッドの分散
・漏れが発生する、テストしにくい
・既存に継ぎ足し
・既存メソッドに振る舞いを追加
・個別最適
・複雑テスト漏れ
計画変更
・複数のマイルストーンごとに異なる税率
・後片付け漏れ
・差し替え対応引数なしでも動作するnull許容
・日付必須でも $date = null
・一つのズレで一日コミュニケーションしながら進めるので気をつけないといけない

学んだこと
・システム全体において、結合テストしたがエラーの見逃し
・コードで保証ができることをしないといけない
・既存に追加する形でも考えて作り直さないといけない
・未使用変数がバグに繋がっているなどを学んだ

最近やっていること
・PHPによるデザインパターンをやりなおしている
・クラス、依存関係
・Clean Architecture
・SOLID原則
・DI依存関係の逆転
・一方向の依存性

やってみたいこと
・クリーンアーキテクチャ実践 Go
・マイクロサービス
・フロントアーキテクチャ Flux

ドメイン駆動開発の手法をかじった上でLでざパターンについて考える
@manamin0521m

導入
・増田論
・DDDのイベントもある
・メリット
・どのようにロジックが書いてあるか明確
・影響範囲を特定のドメインオブジェクトやパッケージに閉じ込めやすくなる
・三層の記述が完結でわかりやすくなる
・View Controller Model

ドメインとは
・概念である
・概念はオブジェクトの集合体である
・レンタル料金の計算
・before ビジネスルールが見えない
・after classが増える
・流れが見やすい

詳細
・業務の関心事をヒト・モノ・コトいう3分類に分ける
・業務の関心事にドメインを切る
・画面やデータベースの都合からは独立して、業務ロジックを整理
・良くないコードだと?
・判定のロジックが複数のclassに重複してしまう
・1つ変更するのに全部変えないといけない
・画面やデータベースの影響範囲が大きくなる
・バグなどが起きるので、テストが必要

Lデザインパターン(Lancersデザインパターン)
・肥大化したモデルの責務を分けるため
・コードを実務のサービス文言にしている

詳しく
・細かくオブジェクトを作ると、条件分岐や前提条件をコントローラーで書かなくてよくなる
・役割は入出力のみに集中できる
・一方、現状だとテーブルとオブジェクトが一対一のものがほとんどで、各テーブルを細かく区切ったり
・テーブル同士をまとめるという作用が強くなっている

発展
・DDD化するには
・値オブジェクト (Value)
・コレクションオブジェクト(配列)
・区分オブジェクト(条件分岐)

まとめ
・設計に時間がかかるけど

・拡張しやすく、ミスが起こりにくい
・詳しい人が土台を整えてからじゃないと取り掛かるのが難しいけど
・ドメインオブジェクトが増えてくるとやりやすくなりそう
・既存の大量のコードと両立させるのが難しいけど
・コアなドメインから少しずつやっていくと良さそう

僕が作りたい世界と現状の問題点
@0xb5951

方向性
・どうやって体をなくしていくか
・最近の熱い話
・この世界めんどくさい
・やりたいことはいっぱいある
・時間が足りない
・寿命を延ばしたい
・強いタンパク質を作る
・ペインは解決されない

Human Augmentation
・ハードウェアで実現する
・ソフトウェアで実現する
・体の機能を切り出す
・思考パターン、声、目、手など
・切り出した機能にスパイスをかける
・他人の思考パターンと混ぜてみる
・喉の発生域を超えた領域での声
・人間にフィードバックする
・体を空間に溶かす
・瞬間移動もできる

熱い例
・HADO
・ARスポーツ
・ゴーストエンジニアリング
・IOA
・テクノロジーやメディアは人間の体の拡張である
・マーシャル・マクルーハン
・問題はかなり多い
・ゴーストをどう管理できるか
・広義での精神体
・攻殻機動隊に近い

自分の形をどうやって保つか
・サルトル
・実在は本質に先立つ
・ソフトウェアの重さをどうやってなくすか
・物理的な依存点が存在する

ER図について実際に考えて作ってみる
@sayanet

マリオーカートのER図について考える
・ランチをER図おもしろい取り組み
・エンティティロード湿布
・ランサーズのER図考えるの面白そう
・人によってやり方は違う
・必要な要素をピックアップ
・マリオーカートのランキングを取りたい
・ホワイトボードでやるといい
・不要なものを削除する
・ビデオ屋だとどんなER図が作れるのかみんなで考えてみよう

社内LANで学ぶネットワーク / 将棋のAIの歴史
@yakitori009

社内LAN MAPの共有
・各ポートのタグVLAN割り当て
・経路制御(ルーティング)

MAC(Media Access Control address)アドレスとは
・ネットワーク機器のハードウェアに一意に割り当てられる物理アドレス
・OSI参照モデルの第二層(データリンク層)にあたる

サブネットマスクの仕組み
・IPアドレスからネットワークアドレスを取得するときに使う

プライベートIPアドレスとは
・企業ネットワークに存在する端末が自由に使用できるIPアドレスのこと

コンピュータ将棋の登場と進化
・1980年代 初心者
・1990年代前半 12~14級
・1990年代後半 アマチュア初段
・2000年代 アマチュア2段以上
・各ボードゲームの局面数と強さ
・計算量の削減
・枝切り
・選択的探索手法
・開発者の棋力が反映される

Bonanzaの登場
・DeepBlueのチェスアルゴリズムの論文を将棋に応用
・プロ棋士と初対局(2007年)
・渡辺 竜王の勝利
・Bonanza亜種の登場(2009年)
・Bonanza作者の保木さんがOSSを公開
・ボンクラーズ
・Ponanza

まとめ
・コンピュータ将棋の強さ
・疲れない、間違えない、うっかりしたミスがない
・恐れない、緊張しない

まとめ

長くなりましたが、いかがだったでしょうか!?ランサーズエンジニアの環境や雰囲気など少しでも伝われば幸いです。技術力を高めたいエンジニアが弊社にはたくさんいますし、学びたいと思う方には最高かと思います。今後は他社さんを巻き込んでLT大会などもやっていければと思いますし、少しでもランサーズで働いてみたいと思ったエンジニアさん、ぜひ下記のWantedlyから話を聞いてみませんか!?

8年間お疲れ様でした!社内サーバーをAWSに移行したお話

adachin|2019年09月20日
AWS

みなさんお久しぶりです。SREのadachin0817です。
今回は歴史あるランサーズの社内サーバーをAWSに2ヶ月ですべて移行することができました。

どのような取り組みとどのように運用しているのか、
サーバー触っているエンジニアさんは気になるところだと思うので公開します!

ランサーズ開発環境の歴史

2011年から開発環境がラックサーバーで動いており、VMware ESXiで仮想サーバー構築して検証していたようです。
歴史を感じる…

そこから数年経って、Dockerとなり、現在の開発環境になるわけですが、
この数年で、色んなエンジニアが稼働しているVMにバッチや、
その他ツール等運用していくことで誰が管理しているか分からず、技術的負債や属人化が見えるようになっていきました。

クラウドに移行するメリットとデメリット

以下、移行することのメリットとデメリットを記載してみました。

・年に一回ゴールデンウィーク中に実施される法定停電時のサーバー停止、および起動作業が要らなくなる
・技術的負債をなくせる/整理できる
・運用コスト削減/サーバー電気代0円へ
・SREチームが管理することで属人化がなくなる
・AWSのコストが上がる

では現時点で動いているもので必要なサーバーを洗い出してみました。

・バッチ5つ(シェルスクリプト/PHP)
・社内DNSサーバー
・dev X 10台(新機能確認用コンテナ)
・弥生会計
・社内ツール 1つ(Rails)

バッチ

バッチは本番で運用しているサーバーがあるので、5つを移行するだけでしたが、
シェルスクリプト内でC言語でコンパイルしているものがあったりしてなかなか手強かったのを思い出します。

社内DNSサーバー

こちらはRoute53にすべて移行しました。登録するレコードが多く、
aws-cliで一気に登録すると楽なので、以下にサンプルを貼りますので参考にしてみてください。

・53.json

{
  "Comment" : "",
  "Changes" : [
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev0.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev1.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev2.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev3.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev4.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev5.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev6.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
 
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev0-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev1-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev2-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev3-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev4-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev5-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
    {"Action":"CREATE","ResourceRecordSet":{"Name":"dev6-hoge.xxxxxx.jp","Type":"A","AliasTarget":{"HostedZoneId":"xxxxxxxxxxxx","DNSName":"dualstack.rproxy-xxxxxxxx.ap-northeast-1.elb.amazonaws.com","EvaluateTargetHealth":false}}},
   ]
}

・run

$ jq . 53.json
$ aws route53 change-resource-record-sets --hosted-zone-id xxxxxxxx --change-batch file://53.json --profile aws
{
    "ChangeInfo": {
        "Id": "/change/xxxxxxxxxxxx",
        "Status": "PENDING",
        "SubmittedAt": "2019-05-07xxxxxxxxxxxxxxxx",
        "Comment": ""
    }
}

dev X (10台)/弥生会計

dev X(10台)に関しては、社内でエンジニア以外でも確認できるランサーズの検証用環境となります。
ローカルの開発環境との差異をなくすため、EC2上にdockerをインストールして構築しました。
ALBでは各ドメインごとにリスナールールを使用してリクエストをターゲットにルーティングしています。

また、インスタンスが立ち上がっているとコストがかかるため、CloudWatch Eventsで深夜に止めて、
Slack上で個人で使いたいときに予約して起動するようAPI Gateway、Lambda、Slack commandを利用して工夫してみました。
LambdaはNode.jsで書いたので参考にしてみてください。(以下画像より)

弥生会計はEC2上にWindows Server 2019をインストールしてリモートデスクトップを利用して運用しています。
バージョン16から19へのバージョンアップにつまずきましたが、
サポートに問い合わせして、無事移行することができました。
その他社内ツールで利用しているサーバーはAnsible化していなかったので一から作りました。

・確認
/ec2 dev1 status
・起動
/ec2 dev1 start
・停止
/ec2 dev1 stop

・index.js

'use strict';

const AWS = require('aws-sdk');
var targetId = '';

// EC2 インスタンスのステータスを確認
function statusEC2Instance(region, instanceId) {
    const ec2 = new AWS.EC2({ region: region });
    const params = {
        InstanceIds: [instanceId],
        DryRun: false
    };
    return new Promise((resolve, reject) => {
        ec2.describeInstances(params, (err, data) => {
            if (err) reject(err);
            else    resolve(data.Reservations[0].Instances[0].State.Name);
        }); 
    });
}

// EC2 インスタンスを起動
function startEC2Instance(region, instanceId) {
    const ec2 = new AWS.EC2({ region: region });
    const params = {
        InstanceIds: [instanceId],
        DryRun: false,
    };
    return new Promise((resolve, reject) => {
        ec2.startInstances(params, (err, data) => {
            if (err) reject(err);
            else     resolve(data.StartingInstances[0].CurrentState.Name);
        }); 
    });
}


// EC2 インスタンスを停止
function stopEC2Instance(region, instanceId) {
    const ec2 = new AWS.EC2({ region: region });
    const params = {
        InstanceIds: [instanceId],
        DryRun: false,
    };
    return new Promise((resolve, reject) => {
        ec2.stopInstances(params, (err, data) => {
            if (err) reject(err);
            else     resolve(data.StoppingInstances[0].CurrentState.Name);
        }); 
    });
}

// 関数指定してインスタンスを制御
function executeControl(ec2Function) {
    var result = '';
    const a = ec2Function(process.env.EC2_REGION, targetId)
        .then(data => {
            result = data;
        }).catch(err => {
            result = { result: 'hoge', data: err };
        });
    return Promise.all([a]).then(() => result );
}

function getSuccessfulResponse(message, result) {
    return {
        "attachments": [
            {
                "color": "#32cd32",
                "title": 'Success',
                "text": message,
            },
            {
                "title": 'Result',
                "text": '```' + result + '```',
            },
        ],
    };
}

function getErrorResponse(message) {
    return {
        "response_type": "ephemeral",
        "attachments": [
            {
                "color": "#ff0000",
                "title": 'Error',
                "text": message,
            },
        ],
    };
}

# インスタンスIDの指定
exports.handler = (event, context, callback) => {
    if (!event.token || event.token !== process.env.SLASH_COMMAND_TOKEN)
        callback(null, getErrorResponse('Invalid token'));
    if (!event.text)
        callback(null, getErrorResponse('Parameter missing'));

    var target = event.text.split(' ')[0];
    switch (target) {
        case 'dev1':
            targetId = 'i-xxxxxxxxxxxx';
            break;
        case 'dev2':
            targetId = 'i-xxxxxxxxxxxx';
            break;
    }
    
    if (event.text.match(/start/)) {
        executeControl(startEC2Instance).then(result => { callback(null, getSuccessfulResponse(target + ' Starting...', result)); });
    } else if (event.text.match(/stop/)) {
        executeControl(stopEC2Instance).then(result => { callback(null, getSuccessfulResponse(target + ' Stopping...', result)); });
    } else if (event.text.match(/status/)) {
        executeControl(statusEC2Instance).then(result => { callback(null, getSuccessfulResponse(target + ' Get status...', result)); });
    } else {
        callback(null, getErrorResponse('Unknown parameters'));
    }
};

まとめ

8年も動いていた社内サーバーの電源を無事にシャットダウンすることができました。
このラックサーバーのおかげでランサーズの一部を担っているかと思うとなかなか感動的です。

社内でサーバーを運用することはコスト削減になりますが、メンテナンスが大変なのと、
オンプレであればハードウェア障害は避けられず、運用に伴う専門性が問われるため 、
クラウドに移行して誰でも運用できるような検証環境を実現できたので、
なかなか達成感がありました!

読んでいただきありがとうございました。
また会いましょう!! See you next time!!

Vuls祭り#5でLTしてきました

adachin|2019年06月18日
SRE

こんにちわ。皆さんお元気ですか!!!
SREのあだちん(@adachin0817)です。

毎年Vuls祭りが行われるのですが、今回始めて運営チームの委員長をやらせていただきました!
今回は令和一発目のVuls祭り#5でLTしてきましてので、スライドぜひご覧ください!

connpass

https://vuls-jp.connpass.com/event/131960/

約100名

近くの方が遊びに来ていただきました!!

特に好評だったのが「まい泉のハンバーガー」
私は5個食べましたよ👍

WordPressの脆弱性を
Vulsで検知できるように!

アジェンダ

  • 自己紹介
  • ランサーズで運用しているWordpressとVuls
  • 近年の脆弱性数と推移
  • Vuls x WordPress
  • インストール方法
  • スキャンとレポート方法
  • Vuls開発環境のご紹介
  • ハンズオン
  • まとめ

まとめ

  • 脆弱性を放って置くと..
  • セキュリティ対応は敏感に!

  • WordPress脆弱性対応している方は見逃すことなし!
  • Vulsのバリューがさらに上がった↑
  • API叩きまくるとエラーが出る→有料にしましょう


それではまた来年のVuls祭りでお会いしましょう!!!バルス!!