Lähes jokaista nykyaikaista ohjelmistoa testataan ennen kuin muutokset viedään käyttäjän saataville. Ohjelmiston käyttäjälle tarjoavaa alustaa nykypäivän DevOps-Cloud-InfraAsCode -hype maailmassakaan ei kuitenkaan usein testata samalla innolla. Vastaukset oleellisiin kysymyksiin kuten "katkeavatko käyttäjän yhteydet" tai "onko split brain mahdollinen" jäävät usein pintapuoliseen manuaaliseen koittamiseen. Infrakoodia voidaan kuitenkin testata ja kokonaisiin järjestelmiin kohdentaa järjestelmätestausta. Aivan kuten ohjelmistokehityksessäkin, oma paikkansa on myös tutkivalla testauksella. Tuotannossa, tottakai.
1 of 40
Download to read offline
More Related Content
Vincit Teatime 2015 - Niko Kurtti: Case Shopify: SaaS:n testaaminen, mihin unohtui toinen puolikas?
2. 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,...
6. 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...
8. 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
9. Esimerkki Java-ohjelman testaamisesta
Ennen pushia:
- Koodianalyysi, Findbugs
- Yksikkö/integraatiotestaus, JUnit
Ennen deployta tuotantoon:
- Järjestelmätestaus, Selenium
- Suorituskykytestaus, JMeter
( - Manuaalinen hyväksymistestaus )
10. 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
12. 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.
13. 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
14. 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
17. 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 )
18. 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ä
19. 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
end
end
20. 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.
21. 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]', :immediately
end
☁ cookbooks [master] stat -c%T -f /u
stat: illegal option -- c
usage: 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...
22. 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')
end
end
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
23. 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
24. describe 'Base Packages' do
%w(
curl ...
).each do |package_name|
describe package(package_name) do
it { should be_installed }
end
end
end
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
25. 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
26. 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
27. 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
28. Esimerkkejä helpereistä
def test_dns(name, machine='dns3')
`#{ssh_command(machine)} -- "ruby -e "require 'socket'; puts Socket.getaddrinfo('#{name}', 80)[0][3]""`.chomp
end
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
end
end
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
end
end
32. 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
33. 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.)
34. 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.
35. 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"
37. 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
38. 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ä
39. tl;dr
Perinteisen 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