조직 개발 가이드
GitLab v19.4요약
Organization 이니셔티브는 GitLab.com과 GitLab Self-Managed 사이의 기능 동등성 확보에 초점을 둡니다. 이 섹션에서는 조직 수준에서 기능을 만들기 전에 고려할 사항을 설명합니다. Organizations를 Beta 로 출시하는 목표 마일스톤은 19.4(2026-09-11)입니다.
Organization 이니셔티브는 GitLab.com과 GitLab Self-Managed 사이의 기능 동등성 확보에 초점을 둡니다.
기능 팀을 위한 지침#
이 섹션에서는 조직 수준에서 기능을 만들기 전에 고려할 사항을 설명합니다.
Organizations를 Beta 로 출시하는 목표 마일스톤은 19.4(2026-09-11)입니다.
이전에는 GitLab Self-Managed의 인스턴스 수준에서 구현한 기능을 GitLab.com의 최상위 그룹용으로 다시 구현해야 했습니다. Organizations는 이 중복을 없앱니다. 이제 기본 방침은 양쪽을 모두 아우르는 조직 수준에서 기능을 만드는 것입니다. 예를 들어 Artifact Registry는 조직을 기준점으로 사용합니다.
모든 기능에 조직 수준 범위가 필요한 것은 아니며, Organizations가 아직 Beta로 출시되지 않았으므로 알아 두어야 할 주요 고려 사항이 있습니다. 자세한 내용은 다음 섹션에서 확인할 수 있습니다.
기능을 만들기 전에 Slack(#g_organizations)으로 팀에 연락해 사용 사례를 논의합니다.
기능에 조직 수준 범위가 필요한지 판단#
모든 기능이 조직 수준에 속하지는 않습니다.
대부분의 기능은 그룹, 프로젝트, 사용자 수준에 계속 묶여 있어야 합니다. 이는 대부분의 기능이 인스턴스나 TLG 수준을 대상으로 하지 않던 기존 방식과 같습니다.
조직 안의 여러 그룹에 걸치는 기능은 그룹 수준으로 범위를 두되 그룹 간 이동을 제공해야 합니다. 이것만으로는 조직 수준 버전을 새로 만들 근거가 되지 않습니다.
조직 수준의 거버넌스나 설정이 분명히 필요한 경우에만 조직 수준에서 기능을 만듭니다.
자세한 내용은 Organizations Charter
(https://docs.google.com/document/d/1ldPftCifCDkdw3_3JKOnFjdwNHGIgbHIbW8HEc92i1Y/edit, 내부 접근 권한 필요)
를 참고합니다.
조직 수준 기능에 맞는 역할 설계#
조직 수준 역할은 그룹 및 프로젝트 역할과 별개입니다. 기능을 설계할 때는 다음을 따릅니다.
- 각 조직 역할(Owner, User)이 수행할 수 있는 동작을 정의합니다.
- 그룹 수준 역할이 조직 수준 역할에 그대로 대응한다고 가정하지 않습니다.
- 기능에 새 조직 수준 권한이 필요한지, 아니면 기존 역할로 충분한지 검토합니다.
자세한 내용은 조직 사용자 문서를 참고합니다.
최상위 그룹을 자체 조직으로 이전하도록 요구(GitLab.com)#
조직 컨텍스트에 의존하는 기능은 TLG가 자체 조직 안에 있어야 합니다. 현재 GitLab.com의 기본 조직에는 TLG 소유자가 Organization Owner가 아닌 TLG가 포함돼 있기 때문입니다.
이를 organization.default? 같은 검사로 강제하지 않습니다. GitLab Self-Managed와 GitLab
Dedicated는 정상적으로 기본 조직 안에서 동작하므로, 그런 검사는 이들까지 막게 됩니다.
대신 조직 수준 기능에 맞는 역할 설계에 설명된
조직 수준 권한을 사용해 인가로 강제합니다.
예를 들어 Organizations::OrganizationPolicy의 organization_owner와 organization_user 조건을 사용해,
Organization Owner만 부여할 수 있는 권한 뒤에 기능을 둡니다.
조직 기반 과금의 제약 고려#
조직 기반 과금은 아직 제공되지 않습니다.
조직 수준에서 과금이나 구독 권한에 의존하는 기능은 만들지 않습니다.
현재 과금과 구독 권한은 조직이 아니라 GitLab.com의 최상위 그룹을 기준으로 범위가 정해지기 때문입니다. 유료 고객 대부분은 GitLab.com에 유료 TLG를 하나만 두므로 이 문제가 드러나는 경우는 드뭅니다. TLG를 새로 만들면 그 TLG는 기존 TLG의 유료 권한을 물려받지 않습니다.
사용자는 GitLab.com에서 TLG를 추가로 만들기 전에 이 사실을 안내 받습니다.
조직 구현을 위한 현재 지원과 예정 지원#
Organizations 팀은 다음 항목을 자동으로 지원하는 변경을 진행하고 있습니다.
- 애플리케이션 수준 조직 격리: 조직 스코핑을 처리하는 ActiveRecord 확장이 제공됩니다. 잠정적으로 FY27-Q2 초에 제공 및 사용이 예정돼 있습니다.
- Sidekiq: Sidekiq 워커 파라미터에
organization_id를 전달할 필요가 없습니다. Sidekiq 워커는 예약 컨텍스트에서 Current Organization을 물려받습니다 - 이벤트·로깅: User, Project, Namespace와 마찬가지로 Organization도 포함됩니다
- 라우팅: 조직 기반 URL(
/o/<organization>접두사)의 활성화·비활성화를 사용할 수 있습니다. - 테스트에서의 조직 사용
특별한 이유가 없다면 각 팀이 이 항목을 직접 구현할 필요는 없습니다.
조직 기능 릴리스#
조직 기능은 organization flag 뒤에서 제공되며, 이 플래그는 Experimental부터 일반 공개(GA)까지 정해진 단계를 차례로 거칩니다.
기능의 대상 범위는 organization flag가 다음 단계로 나아갈수록 넓어지기만 하므로, 앞 단계의 대상이 빠지는 일은 없습니다.
기능에 게이트를 걸고 organization flag를 등록해 단계를 진행시키는 엔지니어링 가이드는 Organizations 릴리스 프로세스를 참고합니다. 현재 롤아웃 중인 organization flag와 그 단계는 Organizations 플랫폼 릴리스 상태를 참고합니다.
데이터베이스 테이블 설계#
샤딩 지침을 참고합니다.
Current.organization 사용#
요청 계층에서 Current.organization 이 올바르게 설정되는지 확인합니다.
자동으로 설정되지 않는 경우에는 아래 절차를 따릅니다.
Current.organization 이 설정되면 ActiveRecord 확장
(gitlab-database-data_isolation)이 이 컨텍스트를 사용해
쿼리를 해당 조직으로 조건부 스코핑합니다.
Current.organization 이 설정되는 곳#
Current.organization은 다음 컨텍스트에서 자동으로 설정됩니다.
- 컨트롤러:
ApplicationController에 모든 요청마다 실행되는before_action :set_current_organization이 포함돼 있습니다. - GraphQL:
GraphqlController는ApplicationController를 상속하므로 같은before_action이 자동으로 적용됩니다. - Grape API:
lib/api/api.rb의 전역before_validation훅이 모든 엔드포인트에서 실행됩니다. 이 훅은X-GitLab-Organization-ID헤더에서 조직을 해석하고, 그다음 인증된 사용자의 조직을 사용하며, 마지막으로 기본 조직으로 대체합니다. - Sidekiq:
job이 큐에 들어갈 때 포착된 조직 컨텍스트에서 설정됩니다.
다음 경우에는 Current.organization을 직접 설정해야 합니다.
-
skip_global_organization_setup!으로 전역 훅을 사용하지 않는 Grape API 클래스. 전역 훅은 개인 액세스 토큰 같은 표준 API 인증에서 조직을 도출합니다. 엔드포인트가 사용자 지정 인증 방식(예: 배포 토큰)을 사용하면 훅이 올바른 조직을 해석할 수 없습니다. 전역 훅을 사용하지 않고 인증된 주체에서 조직을 직접 도출합니다.class MyAPI < ::API::Base skip_global_organization_setup! before do Current.organization = some_custom_method end end -
Rake 태스크나 Rails 콘솔처럼 요청이나 Sidekiq 컨텍스트 밖에서 실행되는 코드.
조직 컨텍스트 전달#
애플리케이션 로직에 Current.organization 이 필요하다면 요청 계층에서 전달해야 합니다.
# In controllers
def create
@group = Groups::CreateService.new(
current_user,
group_params.with_defaults(organization_id: Current.organization.id)
).execute
end
조직 단위로 쿼리 스코핑#
ActiveRecord 확장(gitlab-database-data_isolation)은 조직의 격리 상태에 따라
쿼리를 현재 조직으로 스코핑합니다.
자세한 내용은 조직 데이터 격리를 참고합니다.
조직 라우팅#
조직 범위 경로는 /o/:organization_path/ 패턴을 사용합니다(예: /o/my-org/projects).
이 URL 헬퍼가 있으면 기능의 뷰와 링크를 두 벌로 유지하지 않고도 전역과 조직 안에서 모두 동작합니다.
projects_path와 project_issues_path(@project) 같은 일반적인 비스코프 Rails URL 헬퍼를 사용합니다.
라우팅 계층이 호출 시점에 생성된 URL을 조직 경로 아래에 중첩할지, 아니면 일반 전역 URL을 반환할지 결정합니다.
projects_path # /projects, or /o/my-org/projects if the request resolves to organization my-org
project_issues_path(@project) # /namespace/project/-/issues, or /o/my-org/namespace/project/-/issues
경로 페어링#
구현은 Routing::OrganizationsHelper::MappedHelpers에 있습니다.
경로를 로드할 때 다음을 수행합니다.
- 모든 경로를 훑어 경로에
/o/:organization_path가 포함된 조직 범위 경로를 찾습니다. - 조직 경로 이름에서
organization_또는organizations_접두사를 떼어 같은 이름의 전역 경로와 각 조직 범위 경로를 짝지읍니다. 예를 들어organization_projects_path는projects_path와 짝이 됩니다. 양쪽에 모두 존재하는 이름만 짝지어집니다. - 짝지어진 각 전역 URL 헬퍼의
_path와_url변형을 모두 재정의해, 생성된 URL을 중첩하거나 일반 전역 경로로 대체할 수 있게 합니다. - 원래의
root_url,root_path,group_canonical_url,group_canonical_path헬퍼를 각각unscoped_root_url,unscoped_root_path,unscoped_group_canonical_url,unscoped_group_canonical_path로 보존합니다.
경로 페어링은 컨트롤러와 액션의 일치가 아니라 이름 규칙만으로 이뤄집니다. 그 결과 홈 조직이 격리된 인스턴스 관리자는 인스턴스 관리자 링크가 자신의 조직 아래에 잘못 중첩될 수 있습니다. 이 상황을 아예 피하기 위해 인스턴스 관리자는 격리된 조직의 멤버가 되지 않도록 할 계획입니다.
Current.data_context#
Current.data_context는 현재 요청의 데이터 격리 경계, 즉 요청의 데이터가 한정되는 단일 조직 또는 사용자를 나타냅니다.
요청마다 한 번 CurrentDataContext#set_data_context(app/controllers/concerns/current_data_context.rb)가 설정합니다.
이는 organization: Current.organization_resolver.from_request와 user: current_user로 만들어진 Gitlab::Current::DataContext(lib/gitlab/current/data_context.rb)를 감쌉니다.
Current.organization은 항상 설정되며, 해당하는 조직이 없으면 기본 조직으로 대체됩니다.
Current.data_context는 해당 조직이 실제로 격리된 경우에만 조직으로 해석됩니다.
DataContext#type은 격리된 조직이 해당하면:organization, 사용자는 있으나 격리된 조직이 없으면:user, 그 외에는:nil을 반환합니다.- 격리되지 않은 조직은 경계로 취급되지 않습니다(
Organization#isolated?가 true 여야 합니다). - 현재 사용자의 홈 조직이 격리돼 있으면, 요청이 다른 조직을 지정하더라도 항상 그 조직이 경계가 됩니다.
- 요청 자체가 지정한 조직(예: URL로 지정한 조직)은 사용자 쪽에서 이미 경계를 주장하지 않을 때, 즉 현재 사용자가 없거나 현재 사용자의 홈 조직이 격리되지 않은 경우에만 경계가 됩니다.
자세한 내용은 조직 컨텍스트 설계 문서를 참고합니다.
Current.organization_resolver#
Current.organization_resolver는 Gitlab::Current::Organization(lib/gitlab/current/organization.rb)이며, 요청마다 한 번 CurrentOrganization#set_current_organization(app/controllers/concerns/current_organization.rb)이 설정합니다.
다음을 제공합니다.
from_organization_params: 현재 요청 URL의/o/:organization_path구간이 지정한 조직이 있으면 그 조직.from_request:from_organization_params, 또는 URL 파라미터의 그룹·프로젝트 네임스페이스가 지정한 조직, 또는X-GitLab-Organization-ID헤더가 지정한 조직.organization:from_request, 또는 현재 사용자의 조직, 또는 기본 조직.
중첩할 조직 결정#
짝지어진 헬퍼 호출마다 중첩할 조직 경로는 다음 순서로 결정되며, 해당하는 첫 단계에서 멈춥니다.
Current.data_context가 조직 컨텍스트로 해석되는 경우(type이:organization). 그 조직의 경로를 사용합니다. 이 단계는 아래 두 단계보다 먼저 확인되고 결과를 반환합니다. 홈 조직이 격리된 사용자나 그 자체로 격리된 기준 조직은 해당 조직 밖에서는 존재하지 않으므로, 호출별 명시적 재정의보다도 우선합니다.- 헬퍼 호출에 전달된 명시적
organization_path:키워드 인수. 1단계가 해당하지 않을 때만 사용됩니다. 이 단계는 값의 참 여부가 아니라 키의 존재 여부를 확인하므로,organization_path: nil을 전달하면 3단계라면 중첩됐을 경우에도 일반 전역 경로가 강제됩니다. Current.organization_resolver.from_organization_params로 확인한, 현재 요청 URL 자체가 지정한 조직. 요청 경로에 이미/o/:organization_path구간이 포함된 경우에만 해당합니다.
이 중 어느 단계도 해당하지 않으면 일반 전역 경로를 사용합니다.
명시적 조직 헬퍼#
위의 자동 해석이 적용되지 않는 서비스, 워커, Rake 태스크 등 요청 계층 밖에서는 명시적 조직 헬퍼를 사용합니다. 자동 해석이 고르는 조직이 아닌 다른 조직이 필요한 경우에도 사용합니다.
organization_projects_path(organization_path: 'my-org') # /o/my-org/projects
organization_project_issues_path(@project, organization_path: 'my-org') # /o/my-org/namespace/project/-/issues
projects_path(organization_path: nil) # /projects, even inside an organization-scoped request
아직 조직 범위가 아닌 경로#
일부 경로는 현재 조직 범위에서 사용할 수 없습니다.
- Devise OmniAuth 콜백 - Devise는 동적 구간 아래로 OmniAuth 콜백을 스코핑하는 것을 지원하지 않으므로 전역 수준에 남아 있습니다
- API 경로 - API 엔드포인트는 아직 조직 범위가 아닙니다
조직 격리 테스트#
조직을 테스트하려면 다음 기능 플래그를 활성화합니다.
ui_for_organizationsorg_stage_experimental.org_creation같은 Experimental 단계의 organization flag를 활성화합니다
organization flag에 관한 자세한 내용은 Organizations 릴리스 프로세스를 참고합니다.
기능을 조직 인식형으로 만들 때는 조직 간 데이터 유출이 일어날 수 있는 영역에 특히 주의합니다. 예를 들면 다음과 같습니다.
- 그룹 및 프로젝트 멤버 초대
- 이슈, 머지 리퀘스트, 댓글에서의 사용자 멘션
- 사용자 검색 및 자동 완성 결과
- 조직을 가로지르는 이슈, 머지 리퀘스트, 마일스톤, 레이블 참조
- 결과를 현재 조직으로 스코핑하는 Finder 클래스
개발 환경에서 수동으로 테스트할 때는 눈에 띄는 이름으로 조직을 만들고 관련 데이터에 모두 그 이름을 접두사로 붙이는 방식이 유용합니다. 이렇게 하면 다른 조직의 데이터가 실수로 노출됐는지 눈으로 쉽게 확인할 수 있습니다.
Secret Tanuki라는 조직을 만들고 관련 데이터에 모두 이 이름을 접두사로 붙입니다.
- 조직:
Secret Tanuki - 사용자:
Secret Tanuki User Bob,Secret Tanuki User Alice - 프로젝트:
Secret Tanuki Project X,Secret Tanuki Project Y - 이슈:
Secret Tanuki Issue #42,Secret Tanuki Issue #99 - 그룹:
Secret Tanuki Group - 머지 리퀘스트:
Secret Tanuki MR: Add feature
데이터 유출을 테스트할 때는 UI 나 API 응답에서 Secret Tanuki를 검색합니다. 있어서는 안 될 곳에서 발견되면
조직 간 데이터 유출을 찾아낸 것입니다. 다음과 같은 경우에 특히 유용합니다.
- 검색과 자동 완성 기능 테스트
- 멤버 초대가 조직을 넘어 유출되지 않는지 검증
- 멘션과 참조의 범위가 올바르게 지정됐는지 확인
- 의도치 않은 데이터 노출이 없는지 API 응답 검토
자동화 테스트#
자동화 테스트 전략은 Organizations를 활용한 테스트를 참고합니다.
프론트엔드 지침#
REST API 및 GraphQL 요청#
REST API와 GraphQL 요청에 현재 조직 컨텍스트를 제공하는 데는 별도의 인수가 필요하지 않습니다. 내부적으로 현재 조직은 axios_utils.js#L15와 graphql.js#L183에서 X-GitLab-Organization-ID 헤더로 전달됩니다.
URL#
프론트엔드에서 URL을 하드코딩하거나 직접 조립하지 않습니다. 그렇게 만든 URL은 조직 라우팅을 지원하지 않습니다. 프론트엔드에서 URL을 생성하는 방법은 GitLab의 URL을 참고합니다.
현재 조직 접근#
현재 조직 컨텍스트는 프론트엔드에서 window.gon.current_organization으로 사용할 수 있습니다. 내부적으로 이 값은 gon_helper.rb#L69에서 프론트엔드로 노출됩니다.