Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SaaSのマルチテナント実現方法 / Multitenancy implementation method of SaaS

neko-neko
August 26, 2019

SaaSのマルチテナント実現方法 / Multitenancy implementation method of SaaS

SaaSでどのようにマルチテナントを実現するか

neko-neko

August 26, 2019
Tweet

Other Decks in Technology

Transcript

  1. • ϝϦοτ • ؅ཧָ͕ • ༰қʹ৘ใ࿙͍͑Λ๷͙ͨΊͷϑΟϧλ৚݅Λ૊ΊΔ • σϝϦοτ • ࿦ཧతʹDBΛ෼ׂ͍ͯ͠Δέʔε(databaseΛސ٬ຖʹ༻ҙ͢Δํ๏΍εΩʔϚΛސ٬

    ຖʹ༻ҙ͢Δํ๏) • ސ٬਺ͷ૿ՃͰςʔϒϧ਺͕૿͑ΔͨΊDBϚΠάϨʔγϣϯ࣮ߦ͕࣌ؒ૿͑Δ • ςφϯτຖʹεΩʔϚ͕ҟͳΔλΠϛϯά͕௕͍࣌ؒى͖ಘΔ • ສ͕ҰϚΠάϨʔγϣϯ͕ࣦഊͨ࣌͠ͷϦΧόϦ͕खؒ
  2. • ϝϦοτ • γϯϓϧΏ͑ʹ؅ཧָ͕ • γεςϜશମͷςʔϒϧ૯਺΋཈͑ΒΕΔ • σϝϦοτ • DB෼ׂܕͱൺ΂ͯ৘ใ࿙͍͑Λ๷͙ͨΊͷϑΟϧλΛ૊ΉͷΛߟྀ͠ͳ͘

    ͯ͸͍͚ͳ͍ • ୯७ʹݸʑͷॲཧʹରͯ͠ϑΟϧλ৚݅Λ૊ΈࠐΉͱɺ࿙Ε͕ى͖ͨ࣌ ʹॏେͳηΩϡϦςΟϗʔϧͱͳΓಘΔ • ސ٬਺ͷ૿ՃͰγεςϜશମͷϨΠςϯγ͕ѱԽ͢Δ
  3. ςφϯτຖʹεΩʔϚ͔DB෼ׂ CREATE SCHEMA tenant1; CREATE TABLE tenant1.blogs ( id bigserial

    NOT NULL PRIMARY KEY, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, );
  4. ςφϯτຖʹεΩʔϚ͔DB෼ׂ CREATE SCHEMA tenant1; CREATE TABLE tenant1.blogs ( id bigserial

    NOT NULL PRIMARY KEY, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); ςφϯτຖʹεΩʔϚΛ༻ҙͯ͠ ςʔϒϧΛ࡞੒
  5. ςφϯτຖʹViewΛ༻ҙ͢Δ CREATE TABLE public.blogs ( id bigserial NOT NULL PRIMARY

    KEY, tenant_id bigint NOT NULL, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); CREATE SCHEMA tenant1; CREATE VIEW tenant1.blogs AS SELECT * FROM public.blogs WHERE tenant_id::text = current_setting('application_name');
  6. ςφϯτຖʹViewΛ༻ҙ͢Δ CREATE TABLE public.blogs ( id bigserial NOT NULL PRIMARY

    KEY, tenant_id bigint NOT NULL, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); CREATE SCHEMA tenant1; CREATE VIEW tenant1.blogs AS SELECT * FROM public.blogs WHERE tenant_id::text = current_setting('application_name'); ςφϯτຖʹεΩʔϚΛ༻ҙ͢Δͷ͸มΘΒͳ ͍͕ɺ7JFXΛ࢖ͬͯՄࢹੑͷ੍ݶΛߦ͏ɻ ςφϯτͷૢ࡞͸7JFXͷΈΛ௨ͯ͡$36%͢Δ͜ ͱͰ҆શʹσʔλΛѻ͑Δ
  7. ςφϯτຖʹViewΛ༻ҙ͢Δ CREATE TABLE public.blogs ( id bigserial NOT NULL PRIMARY

    KEY, tenant_id bigint NOT NULL, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); CREATE VIEW public.tenant1_blogs AS SELECT * FROM public.blogs WHERE tenant_id::text = current_setting('application_name');
  8. Row Level Security • ߦ୯ҐηΩϡϦςΟ • ߦຖʹϨίʔυͷࢀর/ૠೖ/ߋ৽/࡟আΛ੍ݶ͢Δ͜ͱ͕ग़དྷΔ • ϙϦγʔʹϚον͠ͳ͍৔߹
 ࢀর:

    ֘౰ͷϨίʔυ͕ฦ٫͞Εͳ͍
 ͦΕҎ֎: ࣮ߦग़དྷͳ͍ • ϙϦγʔ͸SUPERUSER΋͘͠͸BYPASSRLS͕Ξλον͞Εͯ ͍Δϩʔϧͷ৔߹ແࢹ͞ΕΔ
  9. ࢼͯ͠ΈΔ CREATE TABLE public.blogs ( id bigserial NOT NULL PRIMARY

    KEY, tenant_id bigint NOT NULL, title text NOT NULL ); CREATE POLICY blogs_policy ON public.blogs FOR ALL TO app_user USING (tenant_id::text = current_setting(‘application_name', FALSE)); ALTER TABLE public.blogs ENABLE ROW LEVEL SECURITY; INSERT INTO public.blogs (tenant_id, title) VALUES (1, ‘TEST1’); INSERT INTO public.blogs (tenant_id, title) VALUES (1, ‘TEST2’); INSERT INTO public.blogs (tenant_id, title) VALUES (2, ‘TEST3’); INSERT INTO public.blogs (tenant_id, title) VALUES (2, ‘TEST4’); INSERT INTO public.blogs (tenant_id, title) VALUES (3, ‘TEST5’);
  10. ಈ࡞ΛݟΔ(SELECT) -- superuserͷ৔߹ select * from blogs; id | tenant_id

    | title ----+-----------+------- 1 | 1 | TEST1 2 | 1 | TEST2 3 | 2 | TEST3 4 | 2 | TEST4 5 | 3 | TEST5 -- normal userͷ৔߹ select * from blogs; id | tenant_id | title ----+-----------+------- (0 rows) -- normal userͷ৔߹(application_name: 1) SET application_name = '1'; select * from blogs; id | tenant_id | title ----+-----------+------- 1 | 1 | TEST1 2 | 1 | TEST2 (2 rows)
  11. ಈ࡞ΛݟΔ(INSERT) -- superuserͷ৔߹ ໰୊ແ͘ೖΔ -- normal userͷ৔߹ INSERT INTO public.blogs

    (tenant_id, title) VALUES (1, 'TEST1'); ERROR: new row violates row-level security policy for table "blogs" -- normal userͷ৔߹(application_name: 1) SET application_name = ‘1'; INSERT INTO public.blogs (tenant_id, title) VALUES (1, 'TEST1'); INSERT 0 1 -- normal userͷ৔߹(application_name: 2) INSERT INTO public.blogs (tenant_id, title) VALUES (1, 'TEST1'); ERROR: new row violates row-level security policy for table "blogs"
  12. ಈ࡞ΛݟΔ(UPDATE) -- superuserͷ৔߹ UPDATE blogs SET title='UPDATE' WHERE id =

    1; select * from blogs where id = 1; id | tenant_id | title ----+-----------+-------- 1 | 1 | UPDATE -- normal userͷ৔߹ UPDATE blogs SET title='UPDATE' WHERE id = 1; UPDATE 0 <- Ξοϓσʔτ͞Ε͍ͯͳ͍ -- normal userͷ৔߹(application_name: 1) SET application_name = '1'; UPDATE blogs SET title=‘UPDATE' WHERE id = 1; UPDATE 1 <- Ξοϓσʔτ͞Ε͍ͯΔ
  13. ಈ࡞ΛݟΔ(DELETE) -- superuserͷ৔߹ ໰୊ͳ͘࡟আग़དྷΔ -- normal userͷ৔߹ DELETE FROM blogs

    WHERE id = 1; DELETE 0 <- DELETE͞Ε͍ͯͳ͍ -- normal userͷ৔߹(application_name: 1) SET application_name = ‘1’; DELETE FROM blogs WHERE id = 1; DELETE 1 <- DELETE͞Ε͍ͯΔ
  14. Table CREATE TABLE public.blogs ( id bigserial NOT NULL PRIMARY

    KEY, tenant_id bigint NOT NULL, title text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); CREATE TABLE public.posts ( id bigserial NOT NULL PRIMARY KEY, tenant_id bigint NOT NULL, title text NOT NULL, content text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, ); -- ֎෦Ωʔͷఆٛ͸লུ -- tenant has many groups͕͋ΔલఏͰάϧʔϓͷॴଐϝϯόʔΛ؅ཧ͢Δςʔϒϧ CREATE TABLE public.group_users ( id bigserial NOT NULL PRIMARY KEY, tenant_id bigint NOT NULL, group_id bigint NOT NULL, user_id bigint NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, );
  15. Policy — ϙϦγʔͷఆٛ CREATE POLICY blogs_policy ON public.blogs FOR ALL

    TO app_user USING (tenant_id::text = current_setting(‘application_name', FALSE)); CREATE POLICY posts_policy ON public.posts FOR ALL TO app_user USING (tenant_id::text = current_setting('application_name', FALSE)); CREATE POLICY group_users_policy ON public.group_users FOR ALL TO app_user USING (tenant_id::text = current_setting('application_name', FALSE)); WITH CEHCK ( group_id = (SELECT id FROM groups WHERE id = group_id) AND user_id = (SELECT id FROM users WHERE id = user_id) ); —- ϙϦγʔͷ༗ޮԽ ALTER TABLE public.blogs ENABLE ROW LEVEL SECURITY; ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY; ALTER TABLE public.group_users ENABLE ROW LEVEL SECURITY;
  16. Go

  17. Middleware(Τϥʔൈ͖) func ExampleMultitenantMiddleware(db *sql.DB) func(next http.Handler) http.Handler { return func(next

    http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // context͔ΒݱࡏͷςφϯτIDΛऔಘͯ͠ // ίωΫγϣϯϓʔϧͷ઀ଓΛઐ༗͢Δ tenantID := context.CurrentTenantID(ctx) conn, _ := db.Conn(ctx) defer conn.Close() // ηογϣϯ͕ੜ͖͍ͯΔؒ༗ޮʹͳΔม਺ΛDB΁ηοτ // Ҏ߱ͷhandler͸ઐ༗தͷίωΫγϣϯΛ࢖ͬͯΫΤϦΛൃߦ͢Δ conn.ExecContext(ctx, fmt.Sprintf(“SET application_name=%s;”, tenantID)) next.ServeHTTP(w, r.WithContext(context.WithCurrentConnection(ctx, conn))) } return http.HandleFunc(fn) } }