Vincit Teatime 2015 - Niko Kurtti: Case Shopify: SaaS:n testaaminen, mihin unohtui toinen puolikas?
-
Upload
vincitoy -
Category
Technology
-
view
171 -
download
3
Transcript of Vincit Teatime 2015 - Niko Kurtti: Case Shopify: SaaS:n testaaminen, mihin unohtui toinen puolikas?
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