Save 37% off PRO during our Black Friday Sale! »

pyconjp2021-locust

Ee4cc84c4dedde009c013c6d5f1a2e84?s=47 Tatch
October 16, 2021

 pyconjp2021-locust

Ee4cc84c4dedde009c013c6d5f1a2e84?s=128

Tatch

October 16, 2021
Tweet

Transcript

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

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

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

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

  5. -PDVTUͱ͸ʁ  8IBU`T-PDVTU

  6. -PDVTUͱ͸ʁ8IBU`T-PDVTU 1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ w ෛՙࢼݧ໛ٖతʹଟ਺ͷϢʔβΞΫηεΛߦ͍γεςϜͷৼΔ෣͍ΛݟΔ͜ͱ 
 -PBEUFTUJOHJTBTJNVMBUJPOPGBDDFTTJOHCZNVMUJQMFVTFSTUPTFFTZTUFN`TCFIBWJPVST w γφϦΦ΋1ZUIPOίʔυͱͯ͠ॻ͘ 
 5FTUTDFOBSJPTBSFXSJUUFOJO1ZUIPO

    w ͳͷͰɺ1ZUIPOͰग़དྷΔ͜ͱͳΒେମग़དྷΔ 
 4PBOZUIJOHQPTTJCMFJO1ZUIPOJTQPTTJCMFJOUIFMPBEUFTUJOH -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO
  7. -PDVTUͱ͸ʁ8IBU`T-PDVTU 1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ w ๛෋ͳαϯϓϧίʔυ΍ɺศརͳϓϥάΠϯ΋͋Δ 
 -PUTPGFYBNQMFTBOEVTFGVMQMVHJOTBSFBWBJMBCMF 
 IUUQTHJUIVCDPNMPDVTUJPMPDVTUUSFFNBTUFSFYBNQMFT 
 IUUQTHJUIVCDPN4WFOTLB4QFMMPDVTUQMVHJOT

    
 w ࡢ೥ͷ1Z$PO+1ʹͱͯ΋ྑ͍ൃද͕͋ΔͷͰੋඇΈͯ΄͍͠Ͱ͢ 
 "OPUIFSHSFBUUBMLBCPVU-PDVTUJO1Z$PO+1 w ʮ1ZUIPOͰ࢝ΊΔෛՙࢼݧʯIUUQTQZDPOKQFOUJNFUBCMF JE -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO
  8. ͜Εɺ-PDVTUͰͲ͏΍Δͷʁ  )PXUPEP9XJUI-PDVTU

  9. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH ໨ඪઃఆ 
 Set objective ςετઃܭ 
 Write scenario ςετ࣮ࢪ

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

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

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

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

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

    Run tests ݁Ռ෼ੳ Check results ໨ඪୡ੒ʂ 
 Done! νϡʔχϯά 
 Tune your app
  15. 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())
  16. 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
  17. Ϩεϙϯεͷৄࡉͳ֬ೝ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ˠ
  18. Ϩεϙϯεͷৄࡉͳ֬ೝ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
  19. Ϩεϙϯεͷৄࡉͳ֬ೝ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ˣ
  20. Ϩεϙϯεͷৄࡉͳ֬ೝ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
  21. Ϩεϙϯεͷৄࡉͳ֬ೝ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ˣ
  22. Ϩεϙϯεͷৄࡉͳ֬ೝ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
  23. ςετσʔλͷॳظԽ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
  24. ςετσʔλͷॳظԽ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()
  25. 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ɹ
  26. 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
  27. 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
  28. Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST w ϓϩηεͰ͔͚ΒΕΔෛՙʹ͸ݶք͕͋Δ 
 "TJOHMFQSPDFTTDBOBQQMZBMJNJUFEMFWFMPGMPBEPOUIFUBSHFU w λʔήοτ΁ͷϦΫΤετ࣮ߦΛ෼ࢄͤ͞Δ͜ͱͰΑΓߴ͍ෛՙΛ͔͚Δ 
 6TFEJTUSJCVUFEXPSLFSTUPJODSFBTFMPBE w

    ϓϩηεͷىಈ࣌ʹΦϓγϣϯͰࢦఆͰ͖Δ 
 (JWFPQUJPOTUPTQFDJGZJOXIJDINPEFBQSPDFTTTIPVMESVO $ locust —-master 
 $ locust --worker Target worker 1 worker 2 Master
  29. w ֤8PSLFSϊʔυʹ͸ಉ͡MPDVTUGJMFͷ಺༰͕ૹΒΕΔ 
 &BDIXPSLFSOPEFSFDFJWFTUIFTBNFMPDVTUGJMF w φΠʔϒʹ΍Δͱɺಉ͡Ϣʔβ͕Օॴʹੜ͑Δ 
 5IFTBNFVTFSTTQBXOJOEJGGFSFOUOPEFTJGJOJUJBMJ[FEOBJWFMZ w ෛՙࢼݧର৅͕ଟॏϩάΠϯ੍ޚ౳͍ͯ͠Δ৔߹͸஫ҙ

    
 #FDBSFGVMJGZPVSUBSHFUDBSFTUIFOVNCFSPGBDUJWFTFTTJPOTGPSBVTFS Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST Target worker 1 worker 1 Master Alice Alice
  30. w ࣄલʹ࡞੒ͨ͠ϢʔβͷσʔλΛҰՕॴʹஔ͖ɺμϒΓͳ֤͘XPSLFS͔ΒQVMM 
 1MBDFVTFSEBUBJOPOFMPDBUJPOCFGPSFIBOEBOEQVMMGSPNFBDIXPSLFSXJUIPVUEVQMJDBUJPO w "UNPTUPODFͳ2VFVFΛ࢖ͬͨΓڧ੔߹ੑͷ͋ΔσʔλετΞΛ࢖ͬͨΓ 
 6TFBUNPTUPODFNFTTBHFRVFVFPSTUSPOHMZDPOTJTUFOUEBUBTUPSF w ४උ͢Δର৅͕૿͑ͯ͠·͏

    
 :PVOFFEUPQSFQBSFBOENBOBHFNPSFSFTPVSDFT Target worker 1 worker 1 Master Alice Bob Queue/DB Charlie Dan Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST
  31. w ͦΕͧΕͷXPSLFS্ͰϢχʔΫͳϢʔβͷ৘ใΛಈతʹੜ੒͢Δ 
 %ZOBNJDBMMZHFOFSBUFVOJRVFVTFSTPOFBDIOPEF  w 66*%Λ࢖ͬͨΓɺϗετ໊Λ࢖ͬͨΓ 
 VTJOH66*%PSIPTUOBNFPGUIFXPSLFSOPEF w

    ౤ೖ͢Δσʔλ͕ෳࡶͳ৔߹ɺFWFOUIPPLͰͷॳظԽॲཧ΋ෳࡶʹ 
 *OJUJBMJ[BUJPODPEFJOFWFOUIPPLDBOCFDPNQMJDBUFEJGJOJUEBUBJTDPNQMJDBUFE Target worker 1 worker 1 Master Alice_worker1 Alice_worker2 Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST
  32. w ͜͜·Ͱ -PDVTU͸ςετγφϦΦΛ1ZUIPOίʔυͱͯ͠ॻ͚Δ 
 -PDVTUDBOEFGJOFUFTUTDFOBSJPBT1ZUIPODPEFT w ͜͜·Ͱ )FBEMFTTʹಈ͔͢͜ͱͰɺύϥϝʔλ΋ίʔυ؅ཧͰ͖ΔΑ͏ʹ 
 1BSBNFUFSTMJLFUIFOVNCFSPGVTFSTBSFOPXVOEFSWFSTJPODPOUSPM

    w ͞ΒʹਐΊͯɺΫϥελߏ੒૊Μ্ͩͰ࣮ߦ؀ڥ΋ίʔυԽͨ͘͠ͳΔ 
 5IFOZPVNBZXBOUUPNBOBHFJUTFYFDVUJPOFOWJSPONFOUBT$PEF w ,VCFSOFUFTͷ+PC͕ͪΐ͏Ͳྑ͔ͬͨ ͜ͷޙσϞ͠·͢ 
 z+PCzSFTPVSDFPG,VCFSOFUFTNBLFTJUFBTZ Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST
  33. 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
  34. σϞ  %FNP

  35. ·ͱΊ,FZUBLFBXBZT w ΞϓϦέʔγϣϯΛ1ZUIPOͰॻ͍ͨͳΒɺෛՙࢼݧ΋1ZUIPOͰॻ͜͏ 
 *GZPVXSJUFZPVSBQQJO1ZUIPO XIZOPUEPJOHTPGPSMPBEUFTUJOH  w ॻ͖׳ΕͨݴޠͳΒɺ؀ڥ४උ΋ෳࡶͳγφϦΦ΋ࣗ༝ࣗࡏ 


    8SJUFTFUVQTDSJQUBOEUFTUTDFOBSJPJOUIFMBOHVBHFZPVBSFDPOGJEFOUJO w ࠶ݱੑͷ͋ΔܗͰ࣮ߦ͠Α͏ύϥϝʔλ΋؀ڥ΋(JU؅ཧʹ 
 3VOMPBEUFTUJOHXJUISFQSPEVDJCJMJUZ1VUQBSBNFUFSTBOEFOWJSPONFOUTVOEFSWFSTJPODPOUSPM w Ϋϥελߏ੒ͰΑΓڧ͍ෛՙΛ༩͑Α͏ɺ࣮ߦ؀ڥ΋ίʔυԽ͠Α͏ 
 6TFEJTUSJCVUFEXPSLFSTUPQMBDFIJHIFSMPBE BOENBOBHFFYFDVUJPOFOWJSPONFOUXJUI*B$ w εϥΠυͱαϯϓϧίʔυ͸ͪ͜Β 
 4MJEFTBOETBNQMFDPEFTDBOCFGPVOEIFSF w IUUQTTQFBLFSEFDLDPNUBUDIOJDPMBTQZDPOKQMPDVTU w IUUQTHJUIVCDPN5BUDI/JDPMBTQZDPOKQMPDVTU
  36. 5IBOL:PV