Slide 1

Slide 1 text

RailsΞϓϦΛ σϓϩΠ͢Δ CodeDeploy Classmethod, Inc. Yuki Tannai ࣮ફAWSϓϩάϥϛϯά

Slide 2

Slide 2 text

ࣗݾ঺հ • ୮಺༏ل • @yuukigoodman • αʔόͰಈ͘ϓϩάϥϜͱ͔AWS

Slide 3

Slide 3 text

Agenda • σϓϩΠͱ͸ • σϓϩΠΛࢧ͑ΔAWS • CodeDeployͰRailsΛσϓϩΠ

Slide 4

Slide 4 text

σϓϩΠͱ͸

Slide 5

Slide 5 text

γεςϜΛ ར༻Մೳʹ͢Δ ׆ಈશൠͰ͋Δ — ja.wikipedia.org1 1 https://ja.wikipedia.org/wiki/ιϑτ΢ΣΞσϓϩΠϝϯτ

Slide 6

Slide 6 text

• ͨͩίʔυΛμ΢ϯϩʔυͯ͠ىಈ͢Ε͹ྑ͍ ͱ͍͏΋ͷͰ͸ͳ͍ • σϓϩΠʹΑͬͯղܾ͍ͨ͠໰୊͕͋Γɺ ͦΕ͸ϓϩδΣΫτͷਐߦͱڞʹมԽ͢Δ • σϓϩΠ͸αʔϏεఏڙऀͷ໰୊Λղܾ͢Δ • ఏڙऀͷ໰୊ղܾ͸ར༻ऀͷՁ஋ʹͳΓಘΔ • σϓϩΠ͸඼࣭ͷͻͱͭ(QCD) • Quality/Cost/Delivery

Slide 7

Slide 7 text

σϓϩΠࣗಈԽͷಈػ • ස౓͕ଟ͍ • ϛε͕Ͱ͖ͳ͍ • ϧʔνϯϫʔΫ • Ͱ΋౓ʑมΘΔ ࣗಈԽ͠ͳ͍ͱ΍ͬͯΒΜͶʔʂʂ

Slide 8

Slide 8 text

σϓϩΠࣗಈԽͷ؍఺ • Ͳ͔͜ΒͲ͜ʹσϓϩΠ͢Δ͔ • ࣗಈσϓϩΠͷ࣮ߦτϦΨʔ • ͍ͭ୭͕σϓϩΠ͢Δ͔ • ࣦഊΛͲ͏ݕग़͠ɺͲ͜ʹ௨஌͢Δ͔ • Ͳͷ͘Β͍มߋ͕ೖΔ͔

Slide 9

Slide 9 text

σϓϩΠ͸ϓϩδΣΫτͷਐߦͱͱ΋ʹมԽ͢Δ • Ϗδωε: ࢓༷มߋ΍ೲظ୹ॖ • ૊৫: ϝϯόʔͷೖΕସ͑΍૿ݮɺ੒ख़౓ͷෆ ۉҰ͞ • ٕज़: όʔδϣϯΞοϓ΍ύϑΥʔϚϯε ͜ΕΒͷཧ༝ʹΑΓσϓϩΠ΋มߋ͞ΕΔ

Slide 10

Slide 10 text

มߋʹ଱͑͏Δ σϓϩΠࣗಈԽͷ ϓϩάϥϜΛॻ͘

Slide 11

Slide 11 text

ͦͷͨΊͷ໨ඪઃఆͱઃܭ͕େࣄ

Slide 12

Slide 12 text

σϓϩΠΛࢧ͑Δ AWS

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

EC2 • ΦʔιυοΫεͳσϓϩΠλʔήοτ • େ఍ͷ৔߹͸EC2ʹσϓϩΠ͢Δ • Πϯελϯε࡞੒ͱϓϩϏδϣχϯά͕ඞཁ

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Elastic Beanstalk • HerokuΈ͍ͨͳαʔϏε • EC2/RDS/ELBϦιʔεΛ؆୯ʹ؅ཧ͢Δ • ࣮ମ͸CFn • ઐ༻ͷίϚϯυ(eb-cli)͕͋Δ • eb deploy

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Cloud Formation • AWSϦιʔεͷߏ੒؅ཧ • ϒϥ΢β΍CLIͰ΍͍ͬͯΔ͜ͱΛJSONͰهड़ ͢Δͱ࣮ߦͯ͘͠ΕΔ • AWSެࣜఏڙπʔϧͳͷͰ҆৺ • ࠶ݱੑ͕ٻΊΒΕΔ৔߹͸ඇৗʹ༗ޮ

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

EC2 Container Service • EC2Ϋϥελ্Ͱͷίϯςφ࣮ߦΛ؅ཧ • Dockerӡ༻ʹৄ͍͠ਓɺ ͥͻ࠙਌ձͰڭ͍͑ͯͩ͘͞ >< • Container Registry΋౦ژʹདྷͨ

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Code Deploy • EC2΁ͷίʔυͷ഑ஔͱىಈΛ؅ཧ͢ΔαʔϏε • ຊ೔ͷओਓެ • EC2ʹAgentΛΠϯετʔϧ͓ͯ͘͠

Slide 23

Slide 23 text

CodeDeployͰ RailsΛσϓϩΠ

Slide 24

Slide 24 text

େ·͔ͳྲྀΕ • Ansible Playbookͷ࡞੒ • CodeDeployઃఆͷ࡞੒ • Cloud Formation Templateͷ࡞੒ • σϓϩΠͯ͠ΈΔ • ௥Ճཁ݅Λ࣮૷͢Δ • ࣦഊ࣌ͷ௨஌ • Blue Green Deployment

Slide 25

Slide 25 text

Ansible Playbookͷ࡞੒

Slide 26

Slide 26 text

webservers.yml - hosts: all remote_user: ec2-user vars: rails_env: "{{ lookup('env', 'RAILS_ENV') }}" roles: - common - nginx - fluentd - logrotate - ruby - node - rails - ssh

Slide 27

Slide 27 text

roles/common/tasks/main.yml - name: install yum security plugin yum: pkg=yum-plugin-security state=latest sudo: yes - name: update yum security shell: yum -y --security update-minimal sudo: yes

Slide 28

Slide 28 text

roles/common/tasks/main.yml - name: install yum packages yum: name={{ item }} state=present sudo: yes with_items: - gcc-c++ - glibc-headers - openssl-devel - readline - readline-devel - libyaml-devel - libffi-devel - libxml2 - libxslt - libxml2-devel - libxslt-devel - zlib - zlib-devel - mysql-devel - git - ruby-devel

Slide 29

Slide 29 text

roles/node/tasks/main.yml - name: install nodejs yum: name=nodejs enablerepo=epel sudo: yes

Slide 30

Slide 30 text

roles/ruby/tasks/main.yml - name: check rbenv presence command: which rbenv ignore_errors: true register: rbenv_path - name: Install rbenv sudo: yes git: repo=https://github.com/sstephenson/rbenv.git dest={{ rbenv_root }} when: rbenv_path.rc != 0 - name: Install ruby-build sudo: yes git: repo=https://github.com/sstephenson/ruby-build.git dest={{ rbenv_root }}/plugins/ruby-build when: rbenv_path.rc != 0

Slide 31

Slide 31 text

roles/ruby/tasks/main.yml - name: create shims dir sudo: yes file: path={{ rbenv_root }}/shims state=directory when: rbenv_path.rc != 0 - name: create versions dir sudo: yes file: path={{ rbenv_root }}/versions state=directory when: rbenv_path.rc != 0 - name: change owner of install dir sudo: yes command: chgrp -R {{ app_user }} {{ rbenv_root }} when: rbenv_path.rc != 0 - name: change mode of install dir sudo: yes command: chmod -R g+rwxXs {{ rbenv_root }} when: rbenv_path.rc != 0

Slide 32

Slide 32 text

roles/ruby/tasks/main.yml - name: Set rbenv env file sudo: yes copy: src=rbenv.sh dest=/etc/profile.d mode=0775 when: rbenv_path.rc != 0 - name: check ruby version shell: rbenv versions | grep {{ ruby_version }} > /dev/null ; echo $? ignore_errors: true register: ruby_is_installed changed_when: ruby_is_installed.stdout != '0' - name: Install ruby with rbenv shell: bash -lc "rbenv install {{ ruby_version }}" args: creates: "{{ rbenv_root }}/versions/{{ ruby_version }}/bin/ruby"

Slide 33

Slide 33 text

roles/ruby/tasks/main.yml - name: check ruby version shell: ruby -v | grep {{ ruby_version }} > /dev/null ; echo $? register: is_correct_version changed_when: is_correct_version.stdout != '0' - name: Switch Ruby version shell: bash -lc "rbenv global {{ ruby_version }} && rbenv rehash" when: is_correct_version.stdout != '0' - name: update rubygems shell: bash -lc "rbenv exec gem update --system" - name: install common gems shell: bash -lc "rbenv exec gem install bundler io-console --no-ri --no-rdoc" args: creates: "{{ rbenv_root }}/shims/bundle"

Slide 34

Slide 34 text

CodeDeployઃఆͷ࡞੒

Slide 35

Slide 35 text

RAILS_ROOT/appspec.yml version: 0.0 os: linux files: - source: / destination: /home/ec2-user/app permissions: - object: /home/ec2-user pattern: "**" owner: ec2-user group: ec2-user mode: 755

Slide 36

Slide 36 text

RAILS_ROOT/appspec.yml hooks: ApplicationStop: - location: script/codedeploy/deregister_from_elb.sh - location: script/codedeploy/application_stop.sh timeout: 300 runas: ec2-user BeforeInstall: - location: script/codedeploy/before_install.sh timeout: 300 runas: root

Slide 37

Slide 37 text

RAILS_ROOT/appspec.yml AfterInstall: - location: script/codedeploy/after_install.sh timeout: 3600 runas: ec2-user ApplicationStart: - location: script/codedeploy/application_start.sh timeout: 3600 runas: ec2-user ValidateService: - location: script/codedeploy/verify_service.sh timeout: 3600 runas: ec2-user - location: script/codedeploy/register_with_elb.sh

Slide 38

Slide 38 text

ApplicationStop • σϓϩΠલʹΞϓϦΛఀࢭ͢Δ • ELB഑Լͷ৔߹͸ొ࿥ղআ͢Δ • EC2ࣗ਎Λstand byʹ͢Δ • deregister_from_elb.sh͸ awslabsϦϙͰ഑෍

Slide 39

Slide 39 text

ApplicationStop • application_stop.shͰదٓRailsΛఀࢭ͢ Δ • ͜ͷ࣌఺ͰELB͔Βִ཭͞Ε͍ͯΔͷͰࢭΊ ͳͯ͘΋ྑ͍

Slide 40

Slide 40 text

BeforeInstall • ιʔείʔυΛEC2ʹίϐʔ͢Δ • ओʹAnsible(ͱCapistrano)ͷ४උ • appspec.ymlͰrunas: rootΛ๨Εͳ͍

Slide 41

Slide 41 text

BeforeInstall #!/bin/bash yum list installed python-pip &> /dev/null if [ $? != 0 ]; then yum install -y python-pip fi pip list | grep -q ansible if [ $? != 0 ]; then pip install ansible fi mkdir -p /data/app chown -R ec2-user:ec2-user /data/app

Slide 42

Slide 42 text

AfterInstall • ίϐʔͱunzipͷ࣍ʹ࣮ߦ͞ΕΔ • ࣗ෼ͷEC2λάΛಡΜͰઃఆϑΝΠϧΛμ΢ϯ ϩʔυͯ͠ධՁ

Slide 43

Slide 43 text

AfterInstall #!/bin/bash set -u METADATA=`ec2-describe-instances $(ec2-metadata -i | cut -d" " -f 2) --region ap-northeast-1` BUCKET_NAME=`echo "$METADATA" | grep VariablesBucket | cut -f 5` KEY_NAME=`echo "$METADATA" | grep VariablesKey | cut -f 5` sudo aws s3api get-object --bucket $BUCKET_NAME \ --key $KEY_NAME $APPLICATION_VARIABLES_PATH \ --region ap-northeast-1 . $APPLICATION_VARIABLES_PATH /usr/local/bin/ansible-playbook $DESTINATION_PATH/ansible/site.yml \ -i $DESTINATION_PATH/ansible/codedeployhosts --connection=local

Slide 44

Slide 44 text

ApplicationStart • Railsͷىಈ४උͱىಈΛߦͳ͏ • ࠓճ͸capistranoͰٵऩ͍ͯ͠Δ

Slide 45

Slide 45 text

ApplicationStart #!/bin/bash set -u . /etc/profile.d/rbenv.sh . $APPLICATION_VARIABLES_PATH cd $DESTINATION_PATH rbenv exec bundle exec cap local deploy

Slide 46

Slide 46 text

ValidateService • ΞϓϦέʔγϣϯͷىಈͱELBొ࿥Λߦͳ͏ • ҎԼͷεΫϦϓτ͕exit code 0ͳΒ࣍ͷ registerwithelb.sh͕૸Δ • registerwithelb.sh΋AWSLabsͰެ։͞ Ε͍ͯΔ #!/bin/bash curl -s http://localhost/health_check

Slide 47

Slide 47 text

Cloud Formation Templateͷ࡞੒

Slide 48

Slide 48 text

ͦΕΛه͢ʹ͸ ༨ന͕଍Γͳ͍

Slide 49

Slide 49 text

΍ͬͯΔ͜ͱ • ELBͷ࡞੒ • Launch Configͷ࡞੒ • User DataͰCodeDeploy AgentͷΠϯε τʔϧ • Auto Scaling Groupͷ࡞੒ • Route53Ϩίʔυͷ࡞੒ ※CodeDeployϦιʔε͸࡞੒ͯ͠ͳ͍

Slide 50

Slide 50 text

Launch Config "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "#!/bin/bash -xe\n", "yum update -y aws-cfn-bootstrap; yum install -y aws-cli\n", "sed -i 's/Defaults requiretty/Defaults:root !requiretty/g' /etc/sudoers\n", "cd /home/ec2-user/\n", "aws s3 cp 's3://aws-codedeploy-us-east-1/latest/codedeploy-agent.noarch.rpm' . ", "|| error_exit 'Failed to download AWS CodeDeploy Agent.'\n", "yum -y install codedeploy-agent.noarch.rpm || error_exit 'Failed to install AWS CodeDeploy Agent.' \n" ] ] } },

Slide 51

Slide 51 text

CFn Create Stack

Slide 52

Slide 52 text

CodeDeployϦιʔεΛखಈͰ࡞੒ • ؀ڥม਺shΛS3ʹஔ͘ • CFnͰͰ͖ͨASGΛࢦఆͯ͠Aplication/ DeploymentGroup࡞੒

Slide 53

Slide 53 text

σϓϩΠͯ͠ΈΔ

Slide 54

Slide 54 text

௥Ճཁ݅: ΑΓҰ૚ͷࣗಈԽ

Slide 55

Slide 55 text

ྲྀΕ • εΫϦϓτͰσϓϩΠ༻zipΛ࡞੒ • S3ʹ഑ஔ • LambdaͰCodeDeploy

Slide 56

Slide 56 text

εΫϦϓτͰσϓϩΠ༻zipΛ࡞੒ gulp.task('clean', function() { return del(['./tmp/dest']); }); gulp.task('zip', function() { return gulp.src('**') .pipe(excludeGitignore()) .pipe(zip(bundleName + '.zip')) .pipe(gulp.dest('./tmp/dest')); }); gulp.task('upload', function(){ var publisher = awspublish.create({ params: { Bucket: BUCKET } }); return gulp.src('./tmp/dest/' + bundleName + '.zip') .pipe(publisher.publish()) .pipe(awspublish.reporter()); });

Slide 57

Slide 57 text

S3ʹzipΛஔ͘ͱಈ͘Lambda exports.handler = (event, context, callback) => { const message = JSON.parse(event.Records[0].Sns.Message); const params = { applicationName: `my-application`, deploymentGroupName: `my-deployment-group`, revision: { revisionType: 'S3', s3Location: { bucket: message.bucketName, bundleType: 'zip', key: message.keyName } } };

Slide 58

Slide 58 text

S3ʹzipΛஔ͘ͱಈ͘Lambda codedeploy.createDeployment(params, (err, data) => { if (err) { console.log(err); callback(err); } else { callback(null, data); } });

Slide 59

Slide 59 text

௥Ճཁ݅: ىಈΛૣ͘͢Δ

Slide 60

Slide 60 text

AMIΛͱΔ • σϓϩΠঢ়ଶͷAMI࡞੒ • CFnमਖ਼

Slide 61

Slide 61 text

௥Ճཁ݅: B-G σϓϩΠ

Slide 62

Slide 62 text

• ELB+ASGΛDeploymentGroupΛ2ͭ࡞੒ • CodeDeloy੒ޭ࣌ʹSNSܦ༝ͰLambdaΛىಈ • LambdaͰRoute53 ALIASϨίʔυͷ޲͖ઌΛ ม͑Δ

Slide 63

Slide 63 text

·ͱΊ

Slide 64

Slide 64 text

CodeDeployͰҎԼΛୡ੒Մೳ • Ansible΍γΣϧεΫϦϓτͷφϨοδΛస༻ Ͱ͖Δ • χʔζʹ߹ΘͤͨσϓϩΠγεςϜΛ࡞ΕΔ