$30 off During Our Annual Pro Sale. View Details »

pyconjp2021-locust

Tatch
October 16, 2021

 pyconjp2021-locust

Tatch

October 16, 2021
Tweet

More Decks by Tatch

Other Decks in Programming

Transcript

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

    View Slide

  2. ฏ੉ୡ໵!5BUDI/JDPMBT
    ೥͔Β+9௨৴ࣾɻ


    1ZUIPO(PͰαʔόαΠυ։ൃͨ͠Γɺ
    "84($1্Ͱ؀ڥ੔͑ͨΓͯ͠·͢ɻ
    ӳޠͱதࠃޠ͕ͪΐͬͱ͚ͩ࿩ͤ·͢ɻ
    ࣗݾ঺հ

    View Slide

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

    View Slide

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

    View Slide

  5. -PDVTUͱ͸ʁ

    8IBU`T-PDVTU

    View Slide

  6. -PDVTUͱ͸ʁ8IBU`T-PDVTU
    1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ
    w ෛՙࢼݧ໛ٖతʹଟ਺ͷϢʔβΞΫηεΛߦ͍γεςϜͷৼΔ෣͍ΛݟΔ͜ͱ

    -PBEUFTUJOHJTBTJNVMBUJPOPGBDDFTTJOHCZNVMUJQMFVTFSTUPTFFTZTUFN`TCFIBWJPVST
    w γφϦΦ΋1ZUIPOίʔυͱͯ͠ॻ͘

    5FTUTDFOBSJPTBSFXSJUUFOJO1ZUIPO
    w ͳͷͰɺ1ZUIPOͰग़དྷΔ͜ͱͳΒେମग़དྷΔ

    4PBOZUIJOHQPTTJCMFJO1ZUIPOJTQPTTJCMFJOUIFMPBEUFTUJOH
    -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO

    View Slide

  7. -PDVTUͱ͸ʁ8IBU`T-PDVTU
    1ZUIPO੡ͷෛՙࢼݧϑϨʔϜϫʔΫ
    w ๛෋ͳαϯϓϧίʔυ΍ɺศརͳϓϥάΠϯ΋͋Δ

    -PUTPGFYBNQMFTBOEVTFGVMQMVHJOTBSFBWBJMBCMF

    IUUQTHJUIVCDPNMPDVTUJPMPDVTUUSFFNBTUFSFYBNQMFT

    IUUQTHJUIVCDPN4WFOTLB4QFMMPDVTUQMVHJOT

    w ࡢ೥ͷ1Z$PO+1ʹͱͯ΋ྑ͍ൃද͕͋ΔͷͰੋඇΈͯ΄͍͠Ͱ͢

    "OPUIFSHSFBUUBMLBCPVU-PDVTUJO1Z$PO+1
    w ʮ1ZUIPOͰ࢝ΊΔෛՙࢼݧʯIUUQTQZDPOKQFOUJNFUBCMF JE
    -PBEUFTUJOHGSBNFXPSLXSJUUFOJO1ZUIPO

    View Slide

  8. ͜Εɺ-PDVTUͰͲ͏΍Δͷʁ

    )PXUPEP9XJUI-PDVTU

    View Slide

  9. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app

    View Slide

  10. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app

    View Slide

  11. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app
    locustfile.pyΛॻ͘

    Write locustfile.py

    View Slide

  12. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app
    Run locust

    View Slide

  13. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app
    ݁ՌΛݟͯϘτϧωοΫΛݟ͚ͭΔ

    Check results and find bottlenecks

    View Slide

  14. ෛՙࢼݧͷྲྀΕ4UFQTUPSVOMPBEUFTUJOH
    ໨ඪઃఆ

    Set objective
    ςετઃܭ

    Write scenario
    ςετ࣮ࢪ


    Run tests
    ݁Ռ෼ੳ


    Check results
    ໨ඪୡ੒ʂ

    Done!
    νϡʔχϯά

    Tune your app

    View Slide

  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())

    View Slide

  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

    View Slide

  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ˠ

    View Slide

  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

    View Slide

  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ˣ

    View Slide

  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

    View Slide

  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ˣ

    View Slide

  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

    View Slide

  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

    View Slide

  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()


    View Slide

  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ɹ

    View Slide

  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

    View Slide

  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


    View Slide

  28. Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST
    w ϓϩηεͰ͔͚ΒΕΔෛՙʹ͸ݶք͕͋Δ

    "TJOHMFQSPDFTTDBOBQQMZBMJNJUFEMFWFMPGMPBEPOUIFUBSHFU
    w λʔήοτ΁ͷϦΫΤετ࣮ߦΛ෼ࢄͤ͞Δ͜ͱͰΑΓߴ͍ෛՙΛ͔͚Δ

    6TFEJTUSJCVUFEXPSLFSTUPJODSFBTFMPBE
    w ϓϩηεͷىಈ࣌ʹΦϓγϣϯͰࢦఆͰ͖Δ

    (JWFPQUJPOTUPTQFDJGZJOXIJDINPEFBQSPDFTTTIPVMESVO
    $ locust —-master

    $ locust --worker
    Target
    worker 1
    worker 2
    Master

    View Slide

  29. w ֤8PSLFSϊʔυʹ͸ಉ͡MPDVTUGJMFͷ಺༰͕ૹΒΕΔ

    &BDIXPSLFSOPEFSFDFJWFTUIFTBNFMPDVTUGJMF
    w φΠʔϒʹ΍Δͱɺಉ͡Ϣʔβ͕Օॴʹੜ͑Δ

    5IFTBNFVTFSTTQBXOJOEJGGFSFOUOPEFTJGJOJUJBMJ[FEOBJWFMZ
    w ෛՙࢼݧର৅͕ଟॏϩάΠϯ੍ޚ౳͍ͯ͠Δ৔߹͸஫ҙ

    #FDBSFGVMJGZPVSUBSHFUDBSFTUIFOVNCFSPGBDUJWFTFTTJPOTGPSBVTFS
    Ϋϥελߏ੒Ͱͷىಈ%JTUSJCVUFEXPSLFST
    Target
    worker 1
    worker 1
    Master
    Alice
    Alice

    View Slide

  30. w ࣄલʹ࡞੒ͨ͠ϢʔβͷσʔλΛҰՕॴʹஔ͖ɺμϒΓͳ֤͘XPSLFS͔ΒQVMM

    1MBDFVTFSEBUBJOPOFMPDBUJPOCFGPSFIBOEBOEQVMMGSPNFBDIXPSLFSXJUIPVUEVQMJDBUJPO
    w "UNPTUPODFͳ2VFVFΛ࢖ͬͨΓڧ੔߹ੑͷ͋ΔσʔλετΞΛ࢖ͬͨΓ

    6TFBUNPTUPODFNFTTBHFRVFVFPSTUSPOHMZDPOTJTUFOUEBUBTUPSF
    w ४උ͢Δର৅͕૿͑ͯ͠·͏

    :PVOFFEUPQSFQBSFBOENBOBHFNPSFSFTPVSDFT
    Target
    worker 1
    worker 1
    Master
    Alice
    Bob
    Queue/DB
    Charlie
    Dan
    Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

    View Slide

  31. w ͦΕͧΕͷXPSLFS্ͰϢχʔΫͳϢʔβͷ৘ใΛಈతʹੜ੒͢Δ

    %ZOBNJDBMMZHFOFSBUFVOJRVFVTFSTPOFBDIOPEF
    w 66*%Λ࢖ͬͨΓɺϗετ໊Λ࢖ͬͨΓ

    VTJOH66*%PSIPTUOBNFPGUIFXPSLFSOPEF
    w ౤ೖ͢Δσʔλ͕ෳࡶͳ৔߹ɺFWFOUIPPLͰͷॳظԽॲཧ΋ෳࡶʹ

    *OJUJBMJ[BUJPODPEFJOFWFOUIPPLDBOCFDPNQMJDBUFEJGJOJUEBUBJTDPNQMJDBUFE
    Target
    worker 1
    worker 1
    Master
    Alice_worker1
    Alice_worker2
    Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

    View Slide

  32. w ͜͜·Ͱ
    -PDVTU͸ςετγφϦΦΛ1ZUIPOίʔυͱͯ͠ॻ͚Δ

    -PDVTUDBOEFGJOFUFTUTDFOBSJPBT1ZUIPODPEFT
    w ͜͜·Ͱ
    )FBEMFTTʹಈ͔͢͜ͱͰɺύϥϝʔλ΋ίʔυ؅ཧͰ͖ΔΑ͏ʹ

    1BSBNFUFSTMJLFUIFOVNCFSPGVTFSTBSFOPXVOEFSWFSTJPODPOUSPM
    w ͞ΒʹਐΊͯɺΫϥελߏ੒૊Μ্ͩͰ࣮ߦ؀ڥ΋ίʔυԽͨ͘͠ͳΔ

    5IFOZPVNBZXBOUUPNBOBHFJUTFYFDVUJPOFOWJSPONFOUBT$PEF
    w ,VCFSOFUFTͷ+PC͕ͪΐ͏Ͳྑ͔ͬͨ ͜ͷޙσϞ͠·͢


    z+PCzSFTPVSDFPG,VCFSOFUFTNBLFTJUFBTZ
    Ϋϥελߏ੒Ͱͷࢼݧ࣮ߦ%JTUSJCVUFEXPSLFST

    View Slide

  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

    View Slide

  34. σϞ

    %FNP

    View Slide

  35. ·ͱΊ,FZUBLFBXBZT
    w ΞϓϦέʔγϣϯΛ1ZUIPOͰॻ͍ͨͳΒɺෛՙࢼݧ΋1ZUIPOͰॻ͜͏

    *GZPVXSJUFZPVSBQQJO1ZUIPO XIZOPUEPJOHTPGPSMPBEUFTUJOH
    w ॻ͖׳ΕͨݴޠͳΒɺ؀ڥ४උ΋ෳࡶͳγφϦΦ΋ࣗ༝ࣗࡏ

    8SJUFTFUVQTDSJQUBOEUFTUTDFOBSJPJOUIFMBOHVBHFZPVBSFDPOGJEFOUJO
    w ࠶ݱੑͷ͋ΔܗͰ࣮ߦ͠Α͏ύϥϝʔλ΋؀ڥ΋(JU؅ཧʹ

    3VOMPBEUFTUJOHXJUISFQSPEVDJCJMJUZ1VUQBSBNFUFSTBOEFOWJSPONFOUTVOEFSWFSTJPODPOUSPM
    w Ϋϥελߏ੒ͰΑΓڧ͍ෛՙΛ༩͑Α͏ɺ࣮ߦ؀ڥ΋ίʔυԽ͠Α͏

    6TFEJTUSJCVUFEXPSLFSTUPQMBDFIJHIFSMPBE BOENBOBHFFYFDVUJPOFOWJSPONFOUXJUI*B$
    w εϥΠυͱαϯϓϧίʔυ͸ͪ͜Β

    4MJEFTBOETBNQMFDPEFTDBOCFGPVOEIFSF
    w IUUQTTQFBLFSEFDLDPNUBUDIOJDPMBTQZDPOKQMPDVTU
    w IUUQTHJUIVCDPN5BUDI/JDPMBTQZDPOKQMPDVTU

    View Slide

  36. 5IBOL:PV

    View Slide