Slide 1

Slide 1 text

Locust࣮ફฤ: PythonͰॻ͘ෛՙࢼݧҰ໰Ұ౴ ฏ੉ ୡ໵ / @TatchNicolas

Slide 2

Slide 2 text

ฏ੉ୡ໵!5BUDI/JDPMBT ೥͔Β+9௨৴ࣾɻ 
 
 1ZUIPO(PͰαʔόαΠυ։ൃͨ͠Γɺ "84($1্Ͱ؀ڥ੔͑ͨΓͯ͠·͢ɻ ӳޠͱதࠃޠ͕ͪΐͬͱ͚ͩ࿩ͤ·͢ɻ ࣗݾ঺հ

Slide 3

Slide 3 text

ʮσʔλΠϯςϦδΣϯεͷྗͰΑΓ๛͔Ͱ҆શͳࣾձΛ૑ΔʯใಓϕϯνϟʔͰ͢ɻ +9௨৴ࣾͬͯʁ"CPVU+913&44$PSQ

Slide 4

Slide 4 text

"HFOEB 1.Locustͱ͸ʁ 2.͜ΕɺLocustͰͲ͏΍Δͷʁ 2.1.Ϩεϙϯεͷৄࡉͳ֬ೝ 2.2.ςετσʔλͷॳظԽ 2.3.ίϚϯυϥΠϯ͔Βͷىಈ 2.4.Ϋϥελߏ੒Ͱͷىಈ 3.σϞɾ·ͱΊ

Slide 5

Slide 5 text

-PDVTUͱ͸ʁ 8IBU`T-PDVTU

Slide 6

Slide 6 text

-PDVTUͱ͸ʁ8IBU`T-PDVTU 1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ w ෛՙࢼݧ໛ٖతʹଟ਺ͷϢʔβΞΫηεΛߦ͍γεςϜͷৼΔ෣͍ΛݟΔ͜ͱ 
 -PBEUFTUJOHJTBTJNVMBUJPOPGBDDFTTJOHCZNVMUJQMFVTFSTUPTFFTZTUFN`TCFIBWJPVST w γφϦΦ΋1ZUIPOίʔυͱͯ͠ॻ͘ 
 5FTUTDFOBSJPTBSFXSJUUFOJO1ZUIPO w ͳͷͰɺ1ZUIPOͰग़དྷΔ͜ͱͳΒେମग़དྷΔ 
 4PBOZUIJOHQPTTJCMFJO1ZUIPOJTQPTTJCMFJOUIFMPBEUFTUJOH -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO

Slide 7

Slide 7 text

-PDVTUͱ͸ʁ8IBU`T-PDVTU 1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ w ๛෋ͳαϯϓϧίʔυ΍ɺศརͳϓϥάΠϯ΋͋Δ 
 -PUTPGFYBNQMFTBOEVTFGVMQMVHJOTBSFBWBJMBCMF 
 IUUQTHJUIVCDPNMPDVTUJPMPDVTUUSFFNBTUFSFYBNQMFT 
 IUUQTHJUIVCDPN4WFOTLB4QFMMPDVTUQMVHJOT 
 w ࡢ೥ͷ1Z$PO+1ʹͱͯ΋ྑ͍ൃද͕͋ΔͷͰੋඇΈͯ΄͍͠Ͱ͢ 
 "OPUIFSHSFBUUBMLBCPVU-PDVTUJO1Z$PO+1 w ʮ1ZUIPOͰ࢝ΊΔෛՙࢼݧʯIUUQTQZDPOKQFOUJNFUBCMF JE -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO

Slide 8

Slide 8 text

͜Εɺ-PDVTUͰͲ͏΍Δͷʁ )PXUPEP9XJUI-PDVTU

Slide 9

Slide 9 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app

Slide 10

Slide 10 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app

Slide 11

Slide 11 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app locustfile.pyΛॻ͘ 
 Write locustfile.py

Slide 12

Slide 12 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app Run locust

Slide 13

Slide 13 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app ݁ՌΛݟͯϘτϧωοΫΛݟ͚ͭΔ 
 Check results and find bottlenecks

Slide 14

Slide 14 text

ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app

Slide 15

Slide 15 text

w ϦΫΤετϨεϙϯε͸͓ೃછΈͷSFRVFTUTͷ΍ΓํͰͰ͖Δ 3FRVFTUTBOESFTQPOTFTXPSLKVTUMJLFXFMMLOPXOMJCSBSZSFRVFTUT Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM from locust import HttpUser, task class YourUser(HttpUser): @task def get_something(self): with self.client.get("/something") as resp: print(resp.json())

Slide 16

Slide 16 text

w 6TFSΠϯελϯε͸ಠཱͨ͠ηογϣϯΛ࣋ͭ 
 &BDIVTFSJOTUBODFIBTJUTPXOTFTTJPO w ϨεϙϯεͷৄࡉΛνΣοΫͯ͠੒ޭࣦഊ൑ఆ͕Ͱ͖Δ 
 $IFDLSFTQPOTFTJOEFUBJMBOENBSLBTTVDDFFEFEGBJMFE Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM class YourUser(HttpUser): @task def get_something(self): self.client.get("/something") User User User Target spawn request response

Slide 17

Slide 17 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() # resp.success() ΋͋Δ 'BTU"1*TBNQMFBQQˣ MPDVTUGJMFˠ

Slide 18

Slide 18 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() # resp.success() ΋͋Δ 'BTU"1*TBNQMFBQQˣ MPDVTUGJMFˠ name, passwordΛड͚औͬͯɺ session id୅ΘΓʹnameͷcookieʹηοτ Receive name and password, set name in cookie as if session id

Slide 19

Slide 19 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() # resp.success() ΋͋Δ MPDVTUGJMFˠ nameͷ஋Λ֬ೝͯ͠ɺϩάΠϯ֬ೝͯ͠Δ෩ Validate the value of “name” in cookie, pretending to checking if a user is authenticated 'BTU"1*TBNQMFBQQˣ

Slide 20

Slide 20 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() # resp.success() ΋͋Δ MPDVTUGJMF UFTUTDFOBSJP ˠ 'BTU"1*TBNQMFBQQˣ ςετ༻Ϣʔβ Users for load testing

Slide 21

Slide 21 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() # resp.success() ΋͋Δ MPDVTUGJMFˠ Ϣʔβ͕εϙʔϯ͢Δͱ͖ʹݺ͹ΕΔ Called when a user is spawned ͜ͷηογϣϯ͸͋ͱͷ@taskͷॲཧʹҾ͖ܧ͕ΕΔ This session is shared to following part decorated with @task 'BTU"1*TBNQMFBQQˣ

Slide 22

Slide 22 text

Ϩεϙϯεͷৄࡉͳ֬ೝ7BMJEBUFSFTQPOTFTJOEFUBJM @app.post("/auth") def auth(user: User, resp: Response): req_body = user.dict() # {"name": "alice1234", "password": "alice1234"} if user_collection.find_one(req_body): resp.status_code = status.HTTP_200_OK resp.set_cookie(key="name", value=user.name) else: resp.status_code = status.HTTP_403_FORBIDDEN @app.get(“/top") def top(resp: Response, name: Optional[str] = Cookie(None)): if name: return {"name": name} resp.status_code = status.HTTP_403_FORBIDDEN return {"message": "please login via /auth"} USERS = [ {"name": “alice”,”password”:”alice1234”}, {"name": “bob”,”password”:”bob5678”}, ] class AuthenticatedUser(HttpUser): def on_start(self): if len(USERS) > 0: user = USERS.pop() logger.info(f"popped user: {user}") self.name = user["name"] self.client.post( "/auth", json={ "name": user["name"], "password": user["password"], }, ) @task def get_name(self): with self.client.get(“/top") as resp: if resp.json()["name"] != self.name: logger.warning("not match") resp.failure() else: resp.successe() 'BTU"1*ˣ MPDVTUGJMFˠ Ϣʔβݻ༗ͷ৘ใΛΠϯελϯεม਺ͱͯ͠อ࣋ Keep user-specific value as an instance variable Ϣʔβ͝ͱʹҟͳΔظ଴஋Λ൑ఆ Validate if the expected value is returned ࣦഊ or ੒ޭͱͯ͠ه࿥ Mark as failed or succeeded

Slide 23

Slide 23 text

ςετσʔλͷॳظԽ1PQVMBUFEBUB w QZUFTUͰ͸GJYUVSFͰ؀ڥ΍σʔλͷ४උɾย෇͚Λ͢Δ 
 :PVVTFGJYUVSFTGPSTFUVQUFBSEPXOJO1ZUFTU w ಉ༷ʹ-PDVTUͰ͸ɺFWFOUIPPLTͰલॲཧɾޙॲཧΛॻ͚Δ 
 6TFFWFOUIPPLTJO-PDVTUGPSUIBU w ςετ։࢝࣌ɾऴྃ࣌Ҏ֎ʹ΋৭ʑɺҰཡ͸ެࣜ%PDΛࢀর 
 $IFDLUIFPGGJDJBMEPDGPSUIFDPNQMFUFMJTUPGBWBJMBCMFIPPLT 
 IUUQTEPDTMPDVTUJPFOTUBCMFBQJIUNMFWFOUIPPLT @events.test_start.add_listener def on_test_start(environment, **kwargs): if isinstance(environment.runner, MasterRunner): setup_database() insert_master_data() 
 insert_some_aditional_data() 
 else: # Do worker node setup

Slide 24

Slide 24 text

ςετσʔλͷॳظԽ1PQVMBUFEBUB w 1ZUIPOͰॻ͘͜ͱͰɺϩʔΧϧ։ൃ༻ͷεΫϦϓτ͔ΒॲཧΛྲྀ༻Ͱ͖Δ 
 3FVTFTFUVQTDSJQUXSJUUFOGPSMPDBMEFWFMPQNFOUJOMPBEUFTUJOH @events.test_start.add_listener def on_test_start(environment, **kwargs): if isinstance(environment.runner, MasterRunner): setup_database() insert_master_data() 
 insert_loadtest_user_data() 
 else: # Do worker node setup @click.command(“setup”) def setup_local_env(): setup_database() insert_master_data()

Slide 25

Slide 25 text

w 8FC6*͚ͩͰͳ͘ίϚϯυϥΠϯ͔ΒىಈͰ͖Δ -PDVTUQSPWJEFTIFBEMFTTNPEFJOBEEJUJPOUP8FC6* w ΄ͱΜͲͷύϥϝʔλ͸MPDVTUDPOGͰࢦఆՄೳ 
 .PTUQBSBNFUFSTDBOCFTQFDJGJFEJOUIFDPOGJHVSBUJPOGJMF w ςετγφϦΦͱ࣮ߦ৚݅ΛҰॹʹ(JU؅ཧͰ͖Δ $PNNJUQBSBNFUFSTXJUIUFTUTDFOBSJPUPHJUSFQPTJUPSZ ίϚϯυϥΠϯ͔Βͷىಈ3VOGSPNDPNNBOEMJOF headless = true users = 123 spawn-rate = 10 host = https://jxpress.net $ locust —-headless \ —-users 123 —-spawn-rate 10 —-host https://jxpress.netɹ

Slide 26

Slide 26 text

w ݁Ռ͸)5.-ɾ$47ͱͯ͠ग़ྗՄೳ 3FTVMUTDBOCFFYQPSUFEBT)5.-PS$47 w ؀ڥΛίϯςφԽͨ͠Γɺ݁ՌΞοϓϩʔυͷࣗಈԽ͕༰қ 
 &BTZUPDPOUBJOFSJ[FMPDVTUFOWJSPONFOU PSUPXSJUFBVUPNBUJPOMJLFVQMPBEJOHSFTVMUT ίϚϯυϥΠϯ͔Βͷىಈ3VOGSPNDPNNBOEMJOF #! /bin/bash locust —-headless --html stats/result.html --csv stats/loadtest aws s3 cp —recursive stats/ s3://your-bucket

Slide 27

Slide 27 text

w ςʔϒϧͰྲྀΕΔϩά͸MPHHFSͰ0''ʹ͢Δͱྑ͍͔΋ 
 $POTPMFTUBUTUBCMFDBODMVUUFSZPVSMPH UVSOJUPGG ίϚϯυϥΠϯ͔Βͷىಈ3VOGSPNDPNNBOEMJOF from locust.stats import console_logger # You can suppress tables in log by disabling stats logger console_logger.disabled = True Name # reqs # fails | Avg Min Max Median | req/s failures/s ------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------ Aggregated 0 0(0.00%) | 0 0 0 0 | 0.00 0.00 Name # reqs # fails | Avg Min Max Median | req/s failures/s ------------------------------------------------------------------------------------------------------------------------------------------ GET / 2 2(100.00%) | 3 2 4 3 | 0.00 0.00 POST /auth 2 2(100.00%) | 22 8 37 9 | 0.00 0.00 ------------------------------------------------------------------------------------------------------------------------------------------ Aggregated 4 4(100.00%) | 13 2 37 4 | 0.00 0.00

Slide 28

Slide 28 text

Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST w ϓϩηεͰ͔͚ΒΕΔෛՙʹ͸ݶք͕͋Δ 
 "TJOHMFQSPDFTTDBOBQQMZBMJNJUFEMFWFMPGMPBEPOUIFUBSHFU w λʔήοτ΁ͷϦΫΤετ࣮ߦΛ෼ࢄͤ͞Δ͜ͱͰΑΓߴ͍ෛՙΛ͔͚Δ 
 6TFEJTUSJCVUFEXPSLFSTUPJODSFBTFMPBE w ϓϩηεͷىಈ࣌ʹΦϓγϣϯͰࢦఆͰ͖Δ 
 (JWFPQUJPOTUPTQFDJGZJOXIJDINPEFBQSPDFTTTIPVMESVO $ locust —-master 
 $ locust --worker Target worker 1 worker 2 Master

Slide 29

Slide 29 text

w ֤8PSLFSϊʔυʹ͸ಉ͡MPDVTUGJMFͷ಺༰͕ૹΒΕΔ 
 &BDIXPSLFSOPEFSFDFJWFTUIFTBNFMPDVTUGJMF w φΠʔϒʹ΍Δͱɺಉ͡Ϣʔβ͕Օॴʹੜ͑Δ 
 5IFTBNFVTFSTTQBXOJOEJGGFSFOUOPEFTJGJOJUJBMJ[FEOBJWFMZ w ෛՙࢼݧର৅͕ଟॏϩάΠϯ੍ޚ౳͍ͯ͠Δ৔߹͸஫ҙ 
 #FDBSFGVMJGZPVSUBSHFUDBSFTUIFOVNCFSPGBDUJWFTFTTJPOTGPSBVTFS Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST Target worker 1 worker 1 Master Alice Alice

Slide 30

Slide 30 text

w ࣄલʹ࡞੒ͨ͠ϢʔβͷσʔλΛҰՕॴʹஔ͖ɺμϒΓͳ֤͘XPSLFS͔ΒQVMM 
 1MBDFVTFSEBUBJOPOFMPDBUJPOCFGPSFIBOEBOEQVMMGSPNFBDIXPSLFSXJUIPVUEVQMJDBUJPO w "UNPTUPODFͳ2VFVFΛ࢖ͬͨΓڧ੔߹ੑͷ͋ΔσʔλετΞΛ࢖ͬͨΓ 
 6TFBUNPTUPODFNFTTBHFRVFVFPSTUSPOHMZDPOTJTUFOUEBUBTUPSF w ४උ͢Δର৅͕૿͑ͯ͠·͏ 
 :PVOFFEUPQSFQBSFBOENBOBHFNPSFSFTPVSDFT Target worker 1 worker 1 Master Alice Bob Queue/DB Charlie Dan Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

Slide 31

Slide 31 text

w ͦΕͧΕͷXPSLFS্ͰϢχʔΫͳϢʔβͷ৘ใΛಈతʹੜ੒͢Δ 
 %ZOBNJDBMMZHFOFSBUFVOJRVFVTFSTPOFBDIOPEF w 66*%Λ࢖ͬͨΓɺϗετ໊Λ࢖ͬͨΓ 
 VTJOH66*%PSIPTUOBNFPGUIFXPSLFSOPEF w ౤ೖ͢Δσʔλ͕ෳࡶͳ৔߹ɺFWFOUIPPLͰͷॳظԽॲཧ΋ෳࡶʹ 
 *OJUJBMJ[BUJPODPEFJOFWFOUIPPLDBOCFDPNQMJDBUFEJGJOJUEBUBJTDPNQMJDBUFE Target worker 1 worker 1 Master Alice_worker1 Alice_worker2 Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

Slide 32

Slide 32 text

w ͜͜·Ͱ -PDVTU͸ςετγφϦΦΛ1ZUIPOίʔυͱͯ͠ॻ͚Δ 
 -PDVTUDBOEFGJOFUFTUTDFOBSJPBT1ZUIPODPEFT w ͜͜·Ͱ )FBEMFTTʹಈ͔͢͜ͱͰɺύϥϝʔλ΋ίʔυ؅ཧͰ͖ΔΑ͏ʹ 
 1BSBNFUFSTMJLFUIFOVNCFSPGVTFSTBSFOPXVOEFSWFSTJPODPOUSPM w ͞ΒʹਐΊͯɺΫϥελߏ੒૊Μ্ͩͰ࣮ߦ؀ڥ΋ίʔυԽͨ͘͠ͳΔ 
 5IFOZPVNBZXBOUUPNBOBHFJUTFYFDVUJPOFOWJSPONFOUBT$PEF w ,VCFSOFUFTͷ+PC͕ͪΐ͏Ͳྑ͔ͬͨ ͜ͷޙσϞ͠·͢ 
 z+PCzSFTPVSDFPG,VCFSOFUFTNBLFTJUFBTZ Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

Slide 33

Slide 33 text

apiVersion: batch/v1 kind: Job metadata: name: locust-master labels: app: locust role: master spec: completions: 1 template: metadata: labels: app: locust role: master spec: restartPolicy: OnFailure containers: - name: locust image: tatchnicolas/pyconjp2021-locust command: - "locust" - "--master" - "--config" - "/var/locust/locust.conf" apiVersion: batch/v1 kind: Job metadata: name: locust-worker labels: app: locust role: worker spec: completions: 2 parallelism: 2 template: metadata: labels: app: locust role: worker spec: restartPolicy: OnFailure containers: - name: locust image: tatchnicolas/pyconjp2021-locust command: - "locust" - "--worker" env: - name: LOCUST_MASTER_NODE_HOST value: locust-master-svc Master ↓ Worker→ Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

Slide 34

Slide 34 text

σϞ %FNP

Slide 35

Slide 35 text

·ͱΊ,FZUBLFBXBZT w ΞϓϦέʔγϣϯΛ1ZUIPOͰॻ͍ͨͳΒɺෛՙࢼݧ΋1ZUIPOͰॻ͜͏ 
 *GZPVXSJUFZPVSBQQJO1ZUIPO XIZOPUEPJOHTPGPSMPBEUFTUJOH w ॻ͖׳ΕͨݴޠͳΒɺ؀ڥ४උ΋ෳࡶͳγφϦΦ΋ࣗ༝ࣗࡏ 
 8SJUFTFUVQTDSJQUBOEUFTUTDFOBSJPJOUIFMBOHVBHFZPVBSFDPOGJEFOUJO w ࠶ݱੑͷ͋ΔܗͰ࣮ߦ͠Α͏ύϥϝʔλ΋؀ڥ΋(JU؅ཧʹ 
 3VOMPBEUFTUJOHXJUISFQSPEVDJCJMJUZ1VUQBSBNFUFSTBOEFOWJSPONFOUTVOEFSWFSTJPODPOUSPM w Ϋϥελߏ੒ͰΑΓڧ͍ෛՙΛ༩͑Α͏ɺ࣮ߦ؀ڥ΋ίʔυԽ͠Α͏ 
 6TFEJTUSJCVUFEXPSLFSTUPQMBDFIJHIFSMPBE BOENBOBHFFYFDVUJPOFOWJSPONFOUXJUI*B$ w εϥΠυͱαϯϓϧίʔυ͸ͪ͜Β 
 4MJEFTBOETBNQMFDPEFTDBOCFGPVOEIFSF w IUUQTTQFBLFSEFDLDPNUBUDIOJDPMBTQZDPOKQMPDVTU w IUUQTHJUIVCDPN5BUDI/JDPMBTQZDPOKQMPDVTU

Slide 36

Slide 36 text

5IBOL:PV