ãã®ã¨ã³ããªã¯Rails developer meetup 2017ã§çºè¡¨ããå 容ãããã°ã¨ãã¦æ¸ãåºãããã®ã§ãã ãµã³ãã«ã®ã¹ãããããå¤ãã®ã§è³æã®ä»£ããã«ã¨ã³ããªã¨ãã¦å ¬éãã¾ãã ã¹ã©ã¤ãç¨ã®markdownãå ã«èµ·ããããã®ãªã®ã§ãå°ãèªã¿è¾ãããããã¾ãããã容赦ãã ããã
ECSã¨ã¯
- Dockerã³ã³ããã稼åããããã®ã¯ã©ã¹ã¿ã管çãã¦ããããµã¼ãã¹
- 使ãããªã½ã¼ã¹ãè¨æ¸¬ããèªåã§ã³ã³ããã®é ç½®å ãã³ã³ããã¼ã«ãã¦ããã
- kubernetesã§ã¯ãªããæè¿ãkubernetesãè¦æ¨©åã£ãæããã£ã¦å²ã¨è¾ã
- ä»ã¯EC2ãå²ã¨ããã¯ã¨ã³ãã«éãã¦è¦ããã®ã ããFargateã«è¶ æå¾
- ECS or EKS :tired_face:
Railsã¢ããªã®Dockerize
ãªã¹ã¹ã¡ã®æ§æ
- å®éã«ãããã¤ããimageã¯ä¸ã¤ã«ãã
- ä¾ãã°stagingãproductionçã®ãããã¤ç°å¢ã®éãã¯ã¤ã¡ã¼ã¸ã§ã¯æèããªã
- æå
ã§éçºããæ§ã®Dockerfileã¯åãã
- ãã£ã¬ã¯ããªãã¦ã³ãããã¹ãç¨ã³ã³ãã¼ãã³ãã®ã¤ã³ã¹ãã¼ã«çã®ãã
FROM ruby:2.4.2 ENV DOCKER 1 # install os package RUN <package install> # install yarn package WORKDIR /yarn COPY package.json yarn.lock /yarn/ RUN yarn install --prod --pure-lockfile && yarn cache clean # install gems WORKDIR /app COPY Gemfile Gemfile.lock /app/ RUN bundle install -j3 --retry 6 --without test development --no-cache --deployment # Rails app directory WORKDIR /app COPY . /app RUN ln -sf /yarn/node_modules /app/node_modules && \ mkdir -p vendor/assets tmp/pids tmp/sockets tmp/sessions && \ cp config/unicorn.rb.production config/unicorn.rb ENTRYPOINT [ \ "prehook", "ruby -v", "--", \ "prehook", "ruby /app/docker/setup.rb", "--" ] CMD ["bundle", "exec", "unicorn_rails", "-c", "config/unicorn.rb"] ARG git_sha1 # ã©ã®ã³ããããªã®ãä¸ããåããæ§ã«ãã RUN echo "${git_sha1}" > revision.log ENV GIT_SHA1 ${git_sha1}
docker build
- circleCI 2.0ã¨ã使ãã®ã¯ãæ軽
- ãã£ãã·ã¥ã§ãããæ§ãªããã«ããµã¼ãã¼ãç¨æãã
- https://github.com/reproio/capistrano-dockerbuild
- capistranoãå©ç¨ãã¦ãªã¢ã¼ããµã¼ãã¼ã§docker buildãè¡ã
- ãªãã¸ããªã¸ã®pushçããµãã¼ããã
assets:precompile
Railsã®Dockeråã«ããã鬼éã®ä¸ã¤
- S3 or CDNãäºåã«æ´åãã¦ãããã¨
- ãã«ãæã«è§£æ±ºããããã«ãèªä½ã¨ã¯ç¬ç«ããã
- docker buildããå¾ã§ãdocker runã§å®è¡ãã
- ãã«ããµã¼ãã¼ã®ããªã¥ã¼ã ããã¦ã³ãããassets:precompileã®ãã£ãã·ã¥ãæ°¸ç¶åãã
- ãã£ãã·ã¥ãã¡ã¤ã«ãæ®ã£ã¦ããã°ãé«éã«ã³ã³ãã¤ã«ãçµãã
- manifestãRAILS_ENVæ¯ã«renameãã¦S3ã«ä¿åãã¦ãã ãã®æãã³ãããã®SHA1ãååã«å«ãã¦ããã(buildæã«argã§ä»ä¸ãããã®)
docker run --rm \ -e RAILS_ENV=<RAILS_ENV> -e RAILS_GROUPS=assets \ -v build_dir/tmp:/app/tmp app_image_tag \ rake \ assets:precompile \ assets:sync \ assets:manifest_upload
prehook
ENTRYPOINTã§å¼·å¶çã«å®è¡ããå¦çã§ç°å¢æ¯ã®å·®ç°ãå¸åãã
- ERBã§è¨å®ãã¡ã¤ã«çæ
- ç§å¿å¤ã®æºå
- assets manifestã®æºå
- ãã£ãRAILS_ENVæ¯ã«ååä»ãã¦uploadãã¦ãã®ãDLãã¦ãã
ç§å¿å¤ã®æ±ã
- è¨å®ãã¡ã¤ã«èªä½ãæå·åãã¦ã¤ã¡ã¼ã¸ã«çªã£è¾¼ã
- ç°å¢å¤æ°ã§ç´æ¥çªã£è¾¼ãã¨ECSã®consoleã«é²åºãã
- å¤ã®ç¨®é¡ãå¤ãã¨ç°å¢å¤æ°ç®¡çããå ´æãå¿ è¦ã«ãªã
- ã³ã³ããèµ·åæã«èµ·åç°å¢ã®æ¨©éã§è¤ååã§ããã¨è¯ã
- prehookã§è¤ååå¦çãè¡ã
yaml_vault
https://github.com/joker1007/yaml_vault
- Rails5ã§å ¥ã£ããencrypted secrets.ymlã®æ¡å¼µç
- AWS-KMS, GCP-KMSã«å¯¾å¿ãã¦ãã
- KMSãå©ç¨ããã¨ç§å¿å¤ã«ã¢ã¯ã»ã¹ã§ãã権éãIAMã§ç®¡çã§ãã
- ã¯ã©ã¹ã¿ã«æå±ãã¦ãããã¼ãã®IAM Roleã§è¤åå
- è¨å®ããã¡ã¤ã«ã«ä¸å åãã¤ã¤å®å ¨ã«ç®¡çã§ãã
- Railsã®å ´åãsecrets.ymlãã¡ã¢ãªä¸ã§è¤ååãã¦èµ·åã§ãã
- ãã¡ã¤ã«ã«å±éå¾ã®å¤ãæ®ããªã
éçºç°å¢
docker-composeã¨ãã£ã¬ã¯ããªãã¦ã³ãã§å·¥å¤«ãã
version: "2" services: datastore: image: busybox volumes: - mysql-data:/var/lib/mysql - vendor_bundle:/app/vendor/bundle - bundle:/app/.bundle app: build: context: . dockerfile: Dockerfile-dev environment: MYSQL_USERNAME: root MYSQL_PASSWORD: password MYSQL_HOST: mysql depends_on: - mysql volumes: - .:/app volumes_from: - datastore tmpfs: /app/tmp/pids mysql: image: mysql environment: MYSQL_ROOT_PASSWORD: password ports: - '3306:3306' volumes_from: - datastore
Macã®å ´å
- ã¡ãªã¿ã«ããªã¥ã¼ã ãã¦ã³ããæ»ã¬ç¨é ãã®ã§ãä½ããã®å·¥å¤«ãå¿ è¦
- dinghyãdocker-syncã§é å¼µã
- ã©ã£ã¡ãè¾ã
- Macæ¨ã¦ãã®ããªã¹ã¹ã¡
俺ã®éçºã¹ã¿ã¤ã«
- éçºç¨Dockerfileã§zshãå種ã³ãã³ããå ¥ãã¦ãã
docker-compose run --service-ports app zsh
- ã·ã§ã«ã¹ã¯ãªããã§èªåã®.zshrcãpecoçãã³ãã¼ã
docker exec zsh
- ãã¡ã¤ã«ã®ç·¨éã ãã¯ãã¹ããã·ã³ã§è¡ããå¾ã¯åºæ¬çã«ã³ã³ããå ã§æä½ãã
set -e container_name=$1 cp_to_container() { if ! docker exec ${container_name} test -e $2; then docker cp -L $1 ${container_name}:$2 fi } cp_to_container ~/.zshrc /root/.zshrc if ! docker exec ${container_name} test -e /usr/bin/peco; then docker exec ${container_name} sh -c "curl -L -o /root/peco.tar.gz https://github.com/peco/peco/releases/download/v0.4.5/peco_linux_amd64.tar.gz && tar xf /root/peco.tar.gz -C /root && cp /root/peco_linux_amd64/peco /usr/bin/peco" fi docker exec -it ${container_name} sh -c "export TERM=${TERM}; exec zsh"
ãããã¤ã®åã«
ECSã®æ¦å¿µã«ã¤ãã¦
TaskDefinition
- 1ã¤ä»¥ä¸ã®ã³ã³ããèµ·åå®ç¾©ã®ã»ãã
- ã¤ã¡ã¼ã¸ãCPUã®ã¡ã¢ãªä½¿ç¨éããã¼ããããªã¥ã¼ã ç
- ç©ççã«åããã¼ãã§åä½ãã
- docker-composeã®è¨å®ä¸å¼ã¿ãããªãã®
- kubernetesã§ããPod
Task
- TaskDefinitionããèµ·åãããã³ã³ãã群
- åä¸ã®TaskDefinitionããè¤æ°èµ·åããã
Service
- Taskãã©ã®ã¯ã©ã¹ã¿ã§ããã¤èµ·åããããå®ç¾©ãã
- ECSãèªåã§ãã®æ°ã«ãªãã¾ã§ãã³ã³ãããç«ã¦ãã殺ããããã
- ã³ã³ããã®èµ·åå®ç¾©ã¯TaskDefinitionãåç §ãã
- ã³ã³ãããèµ·åãããã¼ããALBã¨èªåã§ç´ä»ãã
- kubernetesã«ãä¼¼ãæ¦å¿µããã
ECSã¸ã®ãããã¤ã®åºæ¬
- TaskDefinitionãæ´æ°
- Serviceãæ´æ°
- å¾ã¯ECSã«ä»»ãã
ecs_deploy
https://github.com/reproio/ecs_deploy
- capistrano plugin
- TaskDefinitionã¨Serviceã®æ´æ°ãè¡ã
- Serviceæ´æ°å¾ãããã¤ç¶æ³ãåæããã¾ã§å¾ æ©ãã
- æ´æ°ããTaskDefinitionã®revisionãä»ã®ã¿ã¹ã¯ã§åç §ã§ãã
- TaskDefinitionãServiceã®å®ç¾©ã¯Rubyã®Hashã§è¡ã
Why use Capistrano
- æ¢åã®è³ç£ãå¤æ°ãã
- slackéç¥ã®ããã¯ã¨ã
- ãããã¤ã®ã³ãã³ããå¤åããªã
- è¨å®ãã¡ã¤ã«ã®å ´æãå®ç¾©ã大ããå¤åããªã
å人å¥ã¹ãã¼ã¸ã³ã°ç°å¢
- ã¢ããªãµã¼ãã¼ã ãã§è¯ããªã容æã«å®ç¾å¯è½
- RDBçã®ãã¼ã¿ã¹ãã¢ãåå¥ã«æã¤ãªãè²ã é£ãã
- å¼ç¤¾ã¯ã¢ããªãµã¼ãã¼ã ãåå¥ã«ãããã¤å¯è½
- ãã¼ã¿ã¹ãã¢ãå¼ãå ´åã¯ãã«ã»ããã®ç°å¢ã使ãããããå æãã
ã¤ã³ãã©ã®æºå
terraformçã§ä»¥ä¸ã®ãã®ãæºåãã
- ALBãä¸ã¤ç¨æãã
- å人å¥ã®ãµããã¡ã¤ã³ãRoute53ã«å®ç¾©
- ALBã®Target Groupãå人å¥ã«å®ç¾©
- ALBã®ãã¹ããã¼ã¹ã«ã¼ãã£ã³ã°ãå®ç¾©
ãã®å¾capistranoã«memberã¨ããç°å¢ãå®ç¾©ããåã¡ã³ãã¼ãèªåã®ååã§target_ groupãTaskDefinitionã®ååã使ã£ã¦ãããã¤åºæ¥ãæ§ã«è«¸ã ãå¤æ°åãã
terraformã®ä¾
module "acm" { source = "../acm" } resource "aws_alb" "developers" { name = "developers" internal = false subnets = ["your-subnet-id"] security_groups = ["your-security-group-id"] idle_timeout = 120 } resource "aws_alb_target_group" "per_developer" { count = "${length(var.users)}" name = "${element(var.users, count.index)}" port = 8080 protocol = "HTTP" vpc_id = "your-vpc-id" deregistration_delay = 0 health_check { path = "/health_check" interval = 45 matcher = 200 unhealthy_threshold = 8 } } resource "aws_alb_listener" "developers" { load_balancer_arn = "${aws_alb.developers.arn}" port = 443 protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-2016-08" certificate_arn = "your-certificate-arn" default_action { target_group_arn = "${element(aws_alb_target_group.per_developer.*.arn, 0)}" type = "forward" } } resource "aws_alb_listener_rule" "per_developer" { count = "${length(var.users)}" listener_arn = "${aws_alb_listener.developers.arn}" priority = "${count.index + 1}" action { target_group_arn = "${element(aws_alb_target_group.per_developer.*.arn, count.index)}" type = "forward" } condition { field = "host-header" values = ["${element(var.users, count.index)}-*.example.com"] } } resource "aws_route53_record" "per_developer_app" { count = "${length(var.users)}" zone_id = "${var.zone_id}" name = "${element(var.users, count.index)}-app.${var.domain_name}" type = "A" alias { name = "${aws_alb.developers.dns_name}" zone_id = "${aws_alb.developers.zone_id}" evaluate_target_health = true } }
ãããã¤å®ç¾©ã®ä¾
set :rails_env, :staging set :branch, ENV["GIT_SHA1"] || (`git rev-parse HEAD`).strip set :slackistrano, false raise "require DEVELOPER env" unless ENV["DEVELOPER"] target_group_arn = Aws::ElasticLoadBalancingV2::Client.new.describe_target_groups(names: [ENV["DEVELOPER"]]).target_groups[0].target_group_arn set :ecs_services, [ { cluster: "staging-per-member", name: "#{ENV["DEVELOPER"]}-#{fetch(:rails_env)}", task_definition_name: "#{ENV["DEVELOPER"]}-staging", desired_count: 1, deployment_configuration: {maximum_percent: 100, minimum_healthy_percent: 0}, load_balancers: [ target_group_arn: target_group_arn, container_port: 8080, container_name: "nginx", ], }, ]
Autoscale (è¿ãå ã«ä¸è¦ã«ãªã話)
Fargate Tokyoãªã¼ã¸ã§ã³ã¯ãï¼
- ç¾æç¹ã§ECS Serviceã®ã¹ã±ã¼ã«ã¨EC2ã®ã¹ã±ã¼ã«ã¯ç¬ç«ãã¦ãã
- Serviceå¢ããã¦ãEC2ã®ãã¼ããå¢ãããªãã¨ã³ã³ãããç«ã¦ãã¨ããããªã
- å¢ããã®ã¯ç°¡åã ãæ¸ããæã®å¯¾è±¡ãã³ã³ããã¼ã«ã§ããªã
ã¨ããããã§ããã©ã«ãã§è¯ãæ¹æ³ããªãã
ecs_deploy/ecs_auto_scaler
https://github.com/reproio/ecs_deploy
- CloudWatchããã¼ãªã³ã°ãã¦èªåã§ãªã¼ãã¹ã±ã¼ã«ãã :cry:
- Serviceã®æ°ãå¶å¾¡ããEC2ã®æ°ã¯Serviceã®æ°ã«åããã¦èªåã§åæããã
- ã¹ã±ã¼ã«ã¤ã³ã®éã¯ãã³ã³ãããåä½ãã¦ããªããã¼ããæ¤åºãã¦è½ã¨ã
- ã³ã³ãããæ¢ã¾ãã¾ã§ã¯EC2ã®ãã¼ãã¯è½ã¨ããªã
polling_interval: 60 auto_scaling_groups: - name: ecs-cluster-nodes region: ap-northeast-1 buffer: 1 # ã¿ã¹ã¯æ°ã«å¯¾ããä½å°ã®ã¤ã³ã¹ã¿ã³ã¹æ° services: - name: app-production cluster: ecs-cluster region: ap-northeast-1 auto_scaling_group_name: ecs-cluster-nodes step: 1 idle_time: 240 max_task_count: [10, 25] scheduled_min_task_count: - {from: "1:45", to: "4:30", count: 8} cooldown_time_for_reach_max: 600 min_task_count: 0 upscale_triggers: - alarm_name: "ECS [app-production] CPUUtilization" state: ALARM downscale_triggers: - alarm_name: "ECS [app-production] CPUUtilization (low)" state: OK
ecs_auto_scalerèªä½ãã³ã³ããã«
- ecs_auto_scalerã¯ã·ã³ãã«ãªforegroundããã»ã¹
- ç°¡åãªDockerfileã§ã³ã³ããåå¯è½
- ããã¤èªèº«ãECSã«ãããã¤ãã
ã¾ããFargateã§ä¸è¦ã«ãªãã¨æã
ã³ãã³ãå®è¡ã¨ãã°åé
ECSã«ããã¦ç¹å®ã®ãã¼ãã«ãã°ã¤ã³ããã¨ããã®ã¯è² ãã§ãã rails runnerãrakeãSSHã§å®è¡ã¨ãããã¹ãã§ã¯ãªã ãã®ãµã¼ãã¼ã®éç¨ç®¡çãããªããã°ãªããªããªã
wrapbox
https://github.com/reproio/wrapbox
- ECSç¨ã®ã³ãã³ãRunner
- å端ã«æ±ç¨æ§ãæããããã¨ãããã§ã³ã¼ããå¾®å¦ã«â¦â¦
- TaskDefinitionãçæãç»é²ããå³èµ·åãã
- çµäºã¾ã§ã¹ãã¼ã¿ã¹ããã¼ãªã³ã°ãå¾ æ©ãã
- ã¿ã¹ã¯èµ·å権éã¯IAMã§ã¯ã©ã¹ã¿åä½ã§ç®¡çã§ãã
- æ £ããã¨SSHã¨ããããã¤ãä¸è¦ã§ããã楽
default: region: ap-northeast-1 container_definition: image: "<ecr_url>/app:<%= ENV["GIT_SHA1"]&.chomp || ENV["RAILS_ENV"] %>" cpu: 704 memory: 1408 working_directory: /app entry_point: ["prehook", "ruby /app/docker/setup.rb", "--"] environment: - {name: "RAILS_ENV", value: "<%= ENV["RAILS_ENV"] %>"}
wrapboxã§å®è¡ããã³ãã³ããã°ã®åå¾
- papertrailã«ãã°ã転éããå¥ã¹ã¬ããã§ãã¼ãªã³ã°ãã¦ã³ã³ã½ã¼ã«ã«æµããã¨ãã§ãã
- åççã«ä»ã®ãã°éç´ãµã¼ãã¹ã§ãå®ç¾å¯è½ã ããç¾å¨papertrailããå®è£ ã¯ãªã
default: # çç¥ log_fetcher: type: papertrail # Use PAPERTRAIL_API_TOKEN env for Authentication group: "<%= ENV["RAILS_ENV"] %>" query: wrapbox-default log_configuration: log_driver: syslog options: syslog-address: "tcp+tls://<papertrail-entrypoint>" tag: wrapbox-default
db:migrate
capistranoã®hookãå©ç¨ãwrapboxã§å®è¡ãã
def execute_with_wrapbox(executions) executions.each do |execution| runner = Wrapbox::Runner::Ecs.new({ cluster: execution[:cluster], region: execution[:region], task_definition: execution[:task_definition] }) parameter = { environments: execution[:environment], task_role_arn: execution[:task_role_arn], timeout: execution[:timeout], }.compact runner.run_cmd(execution[:command], **parameter) end end desc "execution command on ECS with wrapbox gem (before deployment)" task :before_deploy do execute_with_wrapbox(Array(fetch(:ecs_executions_before_deploy))) end
set :ecs_executions_before_deploy, -> do # ecs_deployã®çµæããTaskDefãåå¾ rake = fetch(:ecs_registered_tasks)["ap-northeast-1"]["rake"] raise "Not registered new task" unless rake [ { cluster: "app", region: "ap-northeast-1", task_definition: { task_definition_name: "#{rake.family}:#{rake.revision}", main_container_name: "rake" }, command: ["db:ridgepole:apply"], timeout: 600, } ] end
db:migrate -> ridgepole
- migrateã®upã¨downãããã©ã
- ç¹ã«éçºè ç¨ã¹ãã¼ã¸ã³ã°
- ridgepoleãªããããã¤æã«å®è¡ããã ãã§ãã»ã¼åæãã
- ã¨ã©ã¼ãèµ·ãããslackã«ãã°ãåºãã¦ãæåã§ç´ã
- productionã¯diffãããã°ãªãªã¼ã¹ãåæ¢ããã
- diffãè¦ã¦ãªãªã¼ã¹æ å½è ãæåã§DDLãçºè¡ãã
ãã¹ãã¨CI
以ä¸ãåç §ã https://speakerdeck.com/joker1007/dockershi-dai-falsefen-san-rspechuan-jing-falsezuo-rifang