Vincit Teatime 2015 - Niko Kurtti: Case Shopify: SaaS:n testaaminen, mihin unohtui toinen puolikas?

Preview:

Citation preview

SaaS testaaminenMiksi testata vain puolet?

Shopifacts

• Alunperin pelkkä verkkokauppa-alusta, nykyisin tarjoaa myös mahdollisuuden kivijalkamyyntiin.

• 150k kauppiasta, mm. Tesla, Github, LA Lakers, Wikipedia, Kith,...

• 300k RPM+, flash sales >1M RPM• Ruby on Rails, MySQL, Redis, Memcache, Elasticsearch,

Chef, Go, Podding, Multi-DC,...

Downtimeen ei ole varaa

Muutos on tästä huolimatta jatkuvaa

Pelkästään Shopify core deployataan kymmeniä kertoja päivässä.

Infrakoodia / muutoksia tämän lisäksi myös tusina+ päivässä

Ympärillä olevia sovelluksia kymmeniä, teema/sovelluskaupat, julkiwebbi, kuvaskaalain, maksujärjestelmä, jne...

Testauksesta

Ohjelmistokehityksessä arkipäivää

Moderniin kehitykseen kuuluu oleellisesti vähintäänkin yksikkötestaus ja integraatiotestaus CI-järjestelmässä.

Tämän lisäksi löytyy pitkälle kehitettyjä menetelmiä ja sovelluksia aina koodianalyysistä suorituskykytestaukseen.

..puhumattakaan TDD/BDD -leiristäTestaaminen on oletusarvo, ei poikkeus

Esimerkki Java-ohjelman testaamisesta

Ennen pushia:- Koodianalyysi, Findbugs- Yksikkö/integraatiotestaus, JUnit

Ennen deployta tuotantoon:- Järjestelmätestaus, Selenium- Suorituskykytestaus, JMeter

( - Manuaalinen hyväksymistestaus )

Testaus infrapuolella

Ennen pushia:- Mikä versiohallinta..?

Ennen deployta tuotantoon:- Luin Googlen viidennestä hitistä että näin se toimii ja kävin manuaalisesti testaamassa

Tai vaihtoehtoisesti:

Käyttöpalvelutarjoaja vastaa tästä osuudesta

Mutta me tehdään devopsia agilesti?

Vihjeellisissä yrityksissä käytössä CM tuotantoympäristöissä jotka jollain tavalla kiinni versiohallinnassa.

Ohjelmistokehittäjillä Vagrantit, Dockerit, Ansiblet ja devopsreactions auki selaimessa

Hyvistä lähtökohdista huolimatta inframuutoksien testaus kuitenkin harvinaista ja kevyttä happy case testausta omalla työasemalla.

Jos sammutan tämän koneen niin tuo toinen varmaan palvelee? Joo, näinhän se teki kun selaimessa F5:sta kliksuttelin— Senior Enterprise Devops Fullstack Consultant Architect

Päätökset tuotantoon liittyen useimmiten mututuntumalla

Googlen viides hitti, kaveri ja näin tämä edellisessäkin projektissa toimi.

Ei syvällistä ymmärrystä ratkaisujen toiminnasta tai vaikutuksista.

...istuntojen tila, inflight requestien kohtalo, käyttäjälle palautuvat ilmoitukset, MTTR jne

Onko täydelliselläkään koodilla arvoa jos käyttäjät eivät voi

siitä hyötyä?

Infratestauksen mahdollisuudet

Ennen pushia:- Koodianalyysi, Foodcritic- Yksikkö/integraatiotestaus, Chefspec/Serverspec/Test kitchen

Ennen deployta tuotantoon:- Järjestelmätestaus, Minitest- Suorituskykytestaus, mysqlslap, memaslap, redis-benchmark jne.

( - Manuaalinen hyväksymistestaus )

Koodianalyysi, Foodcritic

Koodianalyysityökalu Chef cookbookeille

Todellinen arvo omissa säännöissä

Shopifylla cookbooks repossa 130 contributoria, joten mahdoton varmistua, että kaikki ovat tietoisia kaikista käytännöistä luontaisesti

Kevyt ajaa, ideaalinen integroida esim pre-commit hookkiin Gitissä

rule 'SHOP003', 'Individual email address used; prefer team email addresses' do tags %w{style recipe shopify} cookbook do |path| whitelist = %w(ops admins data-engineering database stack-team ) recipes = Dir["#{path}/{#{standard_cookbook_subdirs.join(',')}}/**/*.rb"] recipes += Dir["#{path}/*.rb"] recipes.collect do |recipe| File.readlines(recipe).collect.with_index do |line, index| if line.match("^((?!#{whitelist.join('|')}|\#).)*@shopify.com") { :filename => recipe, :matched => recipe, :line => index+1, :column => 0 } end end.compact end.flatten endend

Chefspec (yksikkötestaus)

Testaa Chef :n tekemiä muutoksia muistinvaraisesti

CM -työkalut odottavat tietynlaisen tilan (lähtötila tai nykyinen) kun muutoksia ajetaan. Lokaali työasema ei yleensä samassa tilassa palvelinten kanssa.

Tarjoaa perinteisen tarkan yksikkötestaamisen, mutta tilallisuuden vuoksi vaatii paljon vaivaa arvojen mockkaukseen.

Vaikea myöskään pitää mockkeja ja palvelinten tiloja synkassa.

execute "maybe-convert-btrfs-to-overlay" do only_if 'stat -c%T -f /u|grep btrfs' notifies :create, 'cookbook_file[/opt/convert-btrfs-to-overlay]', :immediatelyend

☁ cookbooks [master] stat -c%T -f /u

stat: illegal option -- cusage: stat [-FlLnqrsx] [-f format] [-t timefmt] [file ...]

stat on eri työkalu OSX:llä kuin Linuxilla. Eikä /u ole tietysti edes olemassa saati BTRFS omalla Macbookilla...

context 'btrfs to overlay' do before(:each) do stub_command("stat -c%T -f /u|grep btrfs").and_return(true) end

it 'trigger btrfs conversion to overlay' do expect(chef_run).to run_execute('maybe-convert-btrfs-to-overlay') endend

Mitä tässä testataan? Jos ainoa järkevä testattava asia ( stat -c%T -f /u|grep btrfs ) mockataan mikä arvo jää testille?

Your mileage may vary, Chefspec on todella näppärä apu inhimillisten one-off/typo/if-unless tyyppisten virheiden huomaamiseen

Integraatiotestaus - Test Kitchen/Serverspec

Integraatiotestauksen ero yksikkötestaukseen on se, että yksittäisten ehtojen sijaan testataan tilaa.

Test kitchenissä integraatiot Vagrant, AWS, Docker ja tuki mm. RSpeccille ja Serverspeccille

Järkevää integroida samaan CI-järjestelmään jossa sovellus

describe 'Base Packages' do %w( curl ... ).each do |package_name| describe package(package_name) do it { should be_installed } end endend

describe 'nginx status' do

it 'enables the nginx service' do expect(service 'nginx').to be_enabled end

it 'starts the nginx service' do expect(service 'nginx').to be_running end

it 'is listening on port 9005' do expect(port 9005).to be_listening end

end

describe nginx_version_is_correct(nginx_version) do its(:exit_status) { should eq 0 } its(:stdout) { should match(/nginx version\: nginx\/1\.7\.9/) }end

Järjestelmätestaus, Clusterfuck,

Hyvin yksinkertainen kehikko halutun kokonaisuuden (esim. HA klusteri) testaamiseen.

Fokus toiminnallisuudessa, ei tietyn työkalun toiminnassa. Esim vastaako palvelu requestiin eikä tekikö Chef muutoksen, tai onko palvelu x käynnissä.

• Vagrant - ohjaa virtuaalikoneita/containereita ja provisiointia

• Minitest - testaus

• Raketaskit + hieman Rubyliimaa - helperit

Valitettavasti ei Open Sourcea, mutta idea muutenkin projektia

Vagrantfile DNS-rekursoreita varten

# DNS recursors clusters["dns"].size.each do |i| machine = clusters["dns"].machines["dns#{i}"]

config.vm.define machine.id, primary: false do |config| initialize_machine(config,machine)

config.vm.provision :chef_solo do |chef| set_cookbook_dirs(chef)

# 1,2 are recursors, 3 just a normal node if i > 2 chef.add_role "vagrant-dns-client" else chef.add_role "vagrant-dns-recursor" end end

# dont overwrite resolv.conf with dhclient preserve_resolv = <<-EOF echo 'make_resolv_conf() { : ; }' > /etc/dhcp/dhclient-enter-hooks.d/zzz-preserve-resolv-conf chmod +x /etc/dhcp/dhclient-enter-hooks.d/zzz-preserve-resolv-conf EOF

config.vm.provision "shell", :inline => preserve_resolv end end

Esimerkkitestejä

def setup @io_ip = host_dns_lookup('shopify.io') @com_ip = host_dns_lookup('shopify.com')

for i in 1..CLUSTERS['dns'].machines.size start_pdnsd('start', "dns#{i}") flush_cache("dns#{i}") end

assert_equal @io_ip, test_dns('shopify.io') end

def test__outgoing_dns_block assert_equal @io_ip, test_dns('shopify.io') #warm cache for shopify.io begin block_dns_outgoing('dns3') do assert_equal @io_ip, test_dns('shopify.io') #test cached record refute_equal @com_ip, test_dns('shopify.com') #test record that is not cached end end assert_equal @com_ip, test_dns('shopify.com') #test that upstreams work after block end

Esimerkkejä helpereistä

def test_dns(name, machine='dns3') `#{ssh_command(machine)} -- "ruby -e \\"require 'socket'; puts Socket.getaddrinfo('#{name}', 80)[0][3]\\""`.chompend

def pdnsd_service(cmd, machine) `#{ssh_command(machine)} -- "sudo /etc/init.d/pdnsd #{cmd}"`end

def block_incoming_traffic(machines, &block) begin machines.each do |machine| block_traffic(machine, "53") end yield ensure machines.each do |machine| open_traffic(machine, "53") end endend

def iptables(machine,port,direction, action="-D") ['tcp', 'udp'].each do |proto| ['eth0', 'eth1'].each do |interf| `#{ssh_command(machine)} -- sudo iptables #{action} #{direction} -p #{proto} --dport #{port} -o #{interf} -j DROP >& /dev/null` end endend

Kertauksena

Findbugs <--> FoodcriticJUnit <--> Serverspec/RSpecJärjestelmätestaus <--> Minitest + liimaa

Lisäpisteitä:- SCM -integraatiot (git hookit jne)- CI-integraatiot

Mikä unohtui?

Järjestelmätestaus sovelluksen näkökulmasta

http://www.shopify.com/technology/16906928-building-and-testing-resilient-ruby-on-rails-applications

Toxiproxy ( https://github.com/Shopify/toxiproxy )

Simuloidaan alla oleva infraa ja sen ongelmaskenaarioita.

Ei ota kantaa toteuttaviin teknologioihin (TCP-proxy)

Testit osana sovelluksen testisuitea

test "reconnects on next request after a connection timeout" do old_connect_timeout = ActiveRecord::Base.connection_pool.spec.config[:connect_timeout] assert old_connect_timeout begin ActiveRecord::Base.connection_pool.spec.config[:connect_timeout] = 2 ActiveRecord::Base.clear_all_connections! duration = Benchmark.realtime do assert_statsd_increment("Shopify.mysql.connection_error") do assert_raise(Mysql2::Error) do Toxiproxy[:shopify_test_mysql_master].downstream(:latency, latency: 5000).apply do Sharding.master_connection end end end end assert duration.between?(2, 4) assert_equal 1, Sharding.master_connection.select_value("SELECT 1") ensure ActiveRecord::Base.connection_pool.spec.config[:connect_timeout] = old_connect_timeout ActiveRecord::Base.clear_all_connections! end end

Game Day

Otetaan tutkittavaksi yksittäinen osa palvelusta, esim. Elasticsearch -klusteri tai tietovarastot.

Tarkoituksena löytää tutkittavasta kokonaisuudesta vikoja ja erityisesti ymmärtää miten oma palvelu käyttäytyy vikatilanteissa.

Käytännössä toimii parhaiten mutujen ja oletuksien rikkojana (kahvat, timeoutit, kapasiteetti, palautumisnopeus, palautetut ilmoitukset jne.)

Game Dayn perusteet

Game day on järkevintä suorittaa yhdessä tilassa ja riittävästi aikaa (4-8h) varaten. Paikalle kannattaa kutsua n. 6-12 henkilöä esim seuraavalla jaolla:

• Järjestelmän parhaiten tuntevat (dev/ops/devops/opsdev)

• Järjestelmästä kiinnostuneet ja/tai sen kanssa välillisesti työskentelevät

Dedikoitu fasilisaattori helpottaa tilaisuuden läpivetoa sekä purkua.

Aloitus

1. Piirtäkää yhdessä karkea arkkitehtuurikuva järjestelmästä (riippuvuudet, toimijat jne.)

2. Keksikää yhdessä mahdollisia failureskenaarioita, kone kaatuu/verkossa ongelmia jne.

3. Kirjoittakaa ylös kunkin skenaarion kohdalle mikä vaikutus ongelmalla tulisi olla järjestelmään

• Esimerkiksi "IP siirtyy masterilta slavelle, sovellus palauttaa alle 2 sekunnin ajan 500:ia ja toiminta palautuu"

Testaus

Äänestäkää skenaarioista 5-7 mielenkiintoisinta testattavaksi ja aloittakaa testaus helpoimmasta

Kunkin testin kohdalla kirjatkaa ylös mitä tapahtui

"IP siirtyi masterilta slavelle, mutta slave ei hyväksynyt kirjoituksia. Sovellus ei myöskään palauttanut kahvoja ja jumiutui vapaiden kantakahvojen puutteeseen"

Älkää jääkö tässä kohtaa pohtimaan miksei oletukset toteutuneet

Tulokset

Testauksen tulokset on järkevää käydä läpi välittömästi.

Mahdolliset parannusehdotukset ja -ideat kannattaa myös samassa tilaisuudessa kirjata projektin tikettijärjestelmään.

Tilaisuuden jälkeen on hyvä kirjata yhteenveto tapahtuneesta ja jakaa se vähintään osallistujille. Bonuspisteitä esim. blogikirjoituksen tjsp tekemisestä

tl;drPerinteisen ohjelmistokehityksen tasoiseen kehitykseen infrapuolella ei ole mitään estettä. Työkalut ja metodit ovat lähes identtiset.

Käyttäjän kannalta on merkityksetöntä onko palvelu saavuttamattomissa javabugin vaiko palvelinongelman vuoksi

Kysyttävää?ps. http://www.shopify.com/careers

Recommended