[{"data":1,"prerenderedAt":709},["ShallowReactive",2],{"/en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend/":3,"navigation-en-us":37,"banner-en-us":455,"footer-en-us":471,"Anna Vovchenko":681,"next-steps-en-us":694},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":26,"_id":30,"_type":31,"title":32,"_source":33,"_file":34,"_stem":35,"_extension":36},"/en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Kubernetes overview: Operate cluster data on the frontend","GitLab offers a built-in solution for monitoring your Kubernetes cluster health. Learn more about the technical design and functionality with this detailed guide.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099045/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%2816%29_3L7ZP4GxJrShu6qImuS4Wo_1750099045397.png","https://about.gitlab.com/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Kubernetes overview: Operate cluster data on the frontend\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Anna Vovchenko\"}],\n        \"datePublished\": \"2024-06-20\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Anna Vovchenko","2024-06-20","Accessing real-time cluster information is crucial for verifying successful\nsoftware deployments and initiating troubleshooting processes. In this\narticle, you'll learn about GitLab's enhanced Kubernetes integration,\nincluding how to leverage the Watch API for real-time insights into\ndeployment statuses and streamlined troubleshooting capabilities. \n\n\n## What are GitLab's Kubernetes resources?\n\n\nGitLab offers a dedicated [dashboard for\nKubernetes](https://gitlab.com/groups/gitlab-org/-/epics/2493 \"Visualize the\ncluster state in GitLab\") to understand the status of connected clusters\nwith an intuitive visual interface. It is integrated into the Environment\nDetails page and shows resources relevant to the environment. Currently,\nthree types of Kubernetes resources are available:\n\n\n- pods filtered by the Kubernetes namespace\n\n- services\n\n- Flux resource\n([HelmRelease](https://fluxcd.io/flux/components/helm/helmreleases/) or\n[Kustomization](https://fluxcd.io/flux/components/kustomize/kustomizations/))\n\n\nFor these resources, we provide general information, such as name, status,\nnamespace, age, etc. It is represented similarly to what the\n[kubectl](https://kubernetes.io/docs/reference/kubectl/) command would show\nwhen run from the Kubernetes cluster. More details can be found when\nclicking each resource: The side drawer shows the list of labels,\nannotations, and detailed status and spec information presented as read-only\nYAML code blocks.\n\n\nThe information provided helps to visualize the cluster state, spot any\nissues, and debug problematic deployments right away.\n\n\n## Frontend to cluster communication: The GitLab solution\n\n\nWe have developed a range of tools and solutions to enable a seamless\nconnection and management of Kubernetes clusters within GitLab. One of the\ncore components of this system is the [GitLab agent for\nKubernetes](https://docs.gitlab.com/ee/user/clusters/agent/install/). This\npowerful tool provides a secure bidirectional connection between a GitLab\ninstance and a Kubernetes cluster. It is composed of two main components:\n**agentk** and **KAS** (Kubernetes agent server).\n\n\n![Kubernetes flow\nchart](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099055229.png)\n\n\nagentk is a lightweight cluster-side component. It is responsible for\nestablishing a connection to a KAS instance and waiting for requests to\nprocess. It is proxying requests from KAS to Kubernetes API. It may also\nactively send information about cluster events to KAS.\n\n\nWhile agentk is actively communicating with the cluster, KAS represents a\nGitLab server-side component. It is responsible for:\n\n\n- accepting requests from agentk\n\n- authenticating agentk requests by querying GitLab backend\n\n- fetching the agent's configuration from a corresponding Git repository\nusing Gitaly\n\n- polling manifest repositories for GitOps support\n\n\nWe implemented the agent access rights feature to provide access from the\nGitLab frontend to the cluster in a secure and reliable way. To enable the\nfeature, the user should update the agent’s configuration file by adding the\n[user_access](https://docs.gitlab.com/ee/user/clusters/agent/user_access.html)\nsection with the following parameters: `projects`, `groups`, and `access_as`\nto specify which projects can access cluster information via the agent and\nhow it should authenticate.\n\n\nOnce this is done, the frontend can connect to the cluster by sending a\nrequest to the Rails controller, which should set a `gitlab_kas cookie`.\nThis cookie is then added to the request sent to KAS together with the agent\nID and Cross-Site Request Forgery (CSRF) token. Upon receiving the request,\nKAS checks the user’s authorization and forwards it to agentk, which makes\nan actual request to the Kubernetes API. Then the response goes all the way\nback from the agentk to KAS and finally to the GitLab client.\n\n\n![Kubernetes overview - how it\nworks](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750099055229.png)\n\n\nTo integrate this logic on the GitLab frontend and use it within the Vue\napp, we developed a JavaScript library:\n[@gitlab/cluster-client](https://gitlab.com/gitlab-org/cluster-integration/javascript-client).\nIt is generated from the Kubernetes OpenAPI specification using the\ntypescript-fetch generator. It provides all the Kubernetes APIs in a way\nthat can be used in a web browser.\n\n\n## Introducing the Watch API\n\n\nThe most challenging task is to provide **real-time updates** for the\nKubernetes dashboard. Kubernetes introduces the concept of watches as an\nextension of GET requests, exposing the body contents as a [readable\nstream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams).\nOnce connected to the stream, the Kubernetes API pushes cluster state\nupdates similarly to how the `kubectl get \u003Cresource> --watch` command works.\nThe watch mechanism allows a client to fetch the current state of the\nresource (or resources list) and then subscribe to subsequent changes,\nwithout missing any events. Each event contains a type of modification (one\nof three types: added, modified, or deleted) and the affected object.\n\n\nWithin the `WatchApi` class of the `@gitlab/cluster-client` library, we've\ndeveloped a systematic approach for interacting with the Kubernetes API.\nThis involves fetching a continuous stream of data, processing it line by\nline, and managing events based on their types. Let's explore the key\ncomponents and functionalities of this approach:\n\n\n1. Extending the Kubernetes API: Within the WatchApi class, we extend the\nbase Kubernetes API functionality to fetch a continuous stream of data with\na specified path and query parameters. This extension enables efficient\nhandling of large datasets, as the stream is processed line by line.\n  2. Decoding and event categorization: Upon receiving the stream, each line, typically representing a JSON object, is decoded. This process extracts relevant information and categorizes events based on their types.\n3. Internal data management: The `WatchApi` class maintains an internal data\narray to represent the current state of the streamed data, updating it\naccordingly as new data arrives or changes occur. \n\n4. The `WatchApi` class implements methods for registering event listeners,\nsuch as `onData`, `onError`, `onTimeout`, and `onTerminate`. These methods\nallow developers to customize their application's response to events like\ndata updates, errors, and timeouts. \n\n\nThe code also handles scenarios such as invalid content types, timeouts, and\nerrors from the server, emitting corresponding events for clients to handle\nappropriately. **With this straightforward, event-driven approach, the\n`WatchApi` class allows developers to create responsive real-time\napplications efficiently.**\n\n\n![Kubernetes overview - flow\nchart](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750099055231.png)\n\n\n## How is the Kubernetes overview integrated with the GitLab frontend?\n\n\nCurrently, we have two Kubernetes integrations within the product: the\nKubernetes overview section for the Environments and the full Kubernetes\ndashboard as a separate view. The latter is a major effort of representing\nall the available Kubernetes resources with filtering and sorting\ncapabilities and a detailed view with the full information on the metadata,\nspec, and status of the resource. This initiative is now on hold while we\nare searching for the most useful ways of representing the Kubernetes\nresources related to an environment.\n\n\n[The Kubernetes\noverview](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html)\non the Environments page is a detailed view of the Kubernetes resources\nrelated to a specific environment. To access the cluster state view, the\nuser should select an agent installed in the cluster with the appropriate\naccess rights, provide a namespace (optionally), and select a related Flux\nresource.\n\n\nThe view renders a list of Kubernetes pods and services filtered by the\nnamespace representing their statuses as well as the Flux sync status.\nClicking each resource opens a detailed view with more information for easy\nissue spotting and high-level debugging. \n\n\n![Kubernetes overview - list of Kubernetes pods and\nservices](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099055233.png)\n\n\nWe need to set up a correct configuration object that will be used for all\nthe API requests. In the configuration, we need to specify the URL provided\nby the KAS, that proxies the Kubernetes APIs; the GitLab agent ID to connect\nwith; and the CSRF token. We need to include cookies so that the\n`kas_cookie` gets picked up and sent within the request.\n\n\n```javascript\n\ncreateK8sAccessConfig({ kasTunnelUrl, gitlabAgentId }) {\n  return {\n    basePath: kasTunnelUrl,\n    headers: {\n      'GitLab-Agent-Id': gitlabAgentId,\n      ...csrf.headers,\n    },\n    credentials: 'include',\n  };\n}\n\n```\n\n\nAll the API requests are implemented as GraphQl client queries for\nefficiency, flexibility, and ease of development. The query structure\nenables clients to fetch data from various sources in one request. With\nclear schema definitions, GraphQL minimizes errors and enhances developer\nefficiency.\n\n\nWhen first rendering the Kubernetes overview, the frontend requests static\nlists of pods, services, and Flux resource (either HelmRelease or\nKustomization). The fetch request is needed to render the empty view\ncorrectly. If the frontend tried to subscribe to the Watch API stream and\none of the resource lists was empty, we would wait for the updates forever\nand never show the actual result – 0 resources. In the case of pods and\nservices, after the initial request, we subscribe to the stream even if an\nempty list was received to reflect any cluster state changes. For the Flux\nresource, the changes that the user would expect the resource to appear\nafter the initial request are low. We use the empty response here as an\nopportunity to provide more information about the feature and its setup. \n\n\n![Kubernetes overview - flux sync status\nunavailable](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099055235.png)\n\n\nAfter rendering the initial result, the frontend makes additional requests\nto the Kubernetes API with the `?watch=true` query parameter in the URL. We\ncreate separate watchers for each event type – data, error, or timeout. When\nreceiving the data, we follow three steps:\n\n\n- transform the data\n\n- update the Apollo cache\n\n- run a mutation to update the connection status\n\n\n```javascript\n\nwatcher.on(EVENT_DATA, (data) => {\n  result = data.map(mapWorkloadItem);\n  client.writeQuery({\n    query,\n    variables: { configuration, namespace },\n    data: { [queryField]: result },\n  });\n\n  updateConnectionStatus(client, {\n    configuration,\n    namespace,\n    resourceType: queryField,\n    status: connectionStatus.connected,\n  });\n});\n\n```\n\n\nAs we show the detailed information for each resource, we rely on having the\nstatus, spec, and metadata fields with the annotations and labels included.\nThe Kubernetes API wouldn’t always send this information, which could break\nthe UI and throw errors from the GraphQl client. We transform the received\ndata first to avoid these issues. We also add the `__typename` so that we\ncan better define the data types and simplify the queries by reusing the\nshared fragments.\n\n\nAfter data stabilization, we update the Apollo cache so that the frontend\nre-renders the views accordingly to reflect cluster state changes.\nInterestingly, we can visualize exactly what happens in the cluster – for\nexample, when deleting the pods, Kubernetes first creates the new ones in\nthe pending state, and only then removes the old pods. Thus, for a moment we\ncan see double the amount of pods. We can also verify how the pods proceed\nfrom one state to another in real-time. This is done with the combination of\nadded, deleted, and modified events received from the Kubernetes APIs and\nprocessed in the `WatchApi` class of the `@gitlab/cluster-client` library.\n\n\n![Kubernetes overview - states of connection\nstatus](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099055236.gif)\n\n\nBy default, with a single Watch request, we get a stream of events for five\nminutes, and then it hits the timeout. We need to properly reflect this on\nthe frontend so that the user is aware of any outdated information. To\nachieve this, we introduced a `k8sConnection` query together with\n`reconnectToCluster` mutation. We have a UI element – a badge with a tooltip\nto indicate the connection status. It has three states: connecting,\nconnected, and disconnected. The state gets updated within every step of the\nUX flow. First, we set it to `connecting` once the Watch client gets\ncreated. Then we update it to `connected` with the first received piece of\ndata. Last, we trigger the mutation for `disconnected` state when an error\nor timeout event occurs. This way, we can let the user refresh the view and\nreconnect to the stream without the need of refreshing the browser tab.\nRelying on the user action to reconnect to the stream helps us save\nresources and only request the necessary data while ensuring the accurate\ncluster state is available for the user at any time.\n\n\n## What’s next?\n\n\nLeveraging the Kubernetes built-in functionality for watching the Readable\nstream helped us to build the functionality quickly and provide the\nKubernetes UI solution to our customers, getting early feedback and\nadjusting the product direction. This approach, however, presented technical\nchallenges, such as the inability to utilize the GraphQl subscriptions and\nthe need for reconnecting to the stream.\n\n\nWe are planning our next iterations to enhance the Kubernetes overview\nwithin GitLab UI. One of the planned iterations for the feature,\n[Frontend-friendly Kubernetes Watch\nAPI](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/issues/541),\nis an updated mechanism of batch-watching the cluster data and moving from\nthe fetch Readable stream to WebSockets. We are going to create a new API in\nKAS to expose the Kubernetes watch capability via WebSocket. This should\nreduce the complexity of the JavaScript code, resolve the timeout issue, and\nimprove the compatibility of the Kubernetes APIs within GitLab frontend\nintegrations.\n\n\n> Curious to learn more or want to try out this functionality? Visit our\n[Kubernetes Dashboard\ndocumentation](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html)\nfor more details and configuration tips.\n","engineering",[23,24,25],"kubernetes","features","tutorial",{"slug":27,"featured":28,"template":29},"kubernetes-overview-operate-cluster-data-on-the-frontend",true,"BlogPost","content:en-us:blog:kubernetes-overview-operate-cluster-data-on-the-frontend.yml","yaml","Kubernetes Overview Operate Cluster Data On The Frontend","content","en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend.yml","en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","yml",{"_path":38,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":40,"_id":451,"_type":31,"title":452,"_source":33,"_file":453,"_stem":454,"_extension":36},"/shared/en-us/main-navigation","en-us",{"logo":41,"freeTrial":46,"sales":51,"login":56,"items":61,"search":392,"minimal":423,"duo":442},{"config":42},{"href":43,"dataGaName":44,"dataGaLocation":45},"/","gitlab logo","header",{"text":47,"config":48},"Get free trial",{"href":49,"dataGaName":50,"dataGaLocation":45},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":52,"config":53},"Talk to sales",{"href":54,"dataGaName":55,"dataGaLocation":45},"/sales/","sales",{"text":57,"config":58},"Sign in",{"href":59,"dataGaName":60,"dataGaLocation":45},"https://gitlab.com/users/sign_in/","sign in",[62,106,203,208,313,373],{"text":63,"config":64,"cards":66,"footer":89},"Platform",{"dataNavLevelOne":65},"platform",[67,73,81],{"title":63,"description":68,"link":69},"The most comprehensive AI-powered DevSecOps Platform",{"text":70,"config":71},"Explore our Platform",{"href":72,"dataGaName":65,"dataGaLocation":45},"/platform/",{"title":74,"description":75,"link":76},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":77,"config":78},"Meet GitLab Duo",{"href":79,"dataGaName":80,"dataGaLocation":45},"/gitlab-duo/","gitlab duo ai",{"title":82,"description":83,"link":84},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":85,"config":86},"Learn more",{"href":87,"dataGaName":88,"dataGaLocation":45},"/why-gitlab/","why gitlab",{"title":90,"items":91},"Get started with",[92,97,102],{"text":93,"config":94},"Platform Engineering",{"href":95,"dataGaName":96,"dataGaLocation":45},"/solutions/platform-engineering/","platform engineering",{"text":98,"config":99},"Developer Experience",{"href":100,"dataGaName":101,"dataGaLocation":45},"/developer-experience/","Developer experience",{"text":103,"config":104},"MLOps",{"href":105,"dataGaName":103,"dataGaLocation":45},"/topics/devops/the-role-of-ai-in-devops/",{"text":107,"left":28,"config":108,"link":110,"lists":114,"footer":185},"Product",{"dataNavLevelOne":109},"solutions",{"text":111,"config":112},"View all Solutions",{"href":113,"dataGaName":109,"dataGaLocation":45},"/solutions/",[115,140,164],{"title":116,"description":117,"link":118,"items":123},"Automation","CI/CD and automation to accelerate deployment",{"config":119},{"icon":120,"href":121,"dataGaName":122,"dataGaLocation":45},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[124,128,132,136],{"text":125,"config":126},"CI/CD",{"href":127,"dataGaLocation":45,"dataGaName":125},"/solutions/continuous-integration/",{"text":129,"config":130},"AI-Assisted Development",{"href":79,"dataGaLocation":45,"dataGaName":131},"AI assisted development",{"text":133,"config":134},"Source Code Management",{"href":135,"dataGaLocation":45,"dataGaName":133},"/solutions/source-code-management/",{"text":137,"config":138},"Automated Software Delivery",{"href":121,"dataGaLocation":45,"dataGaName":139},"Automated software delivery",{"title":141,"description":142,"link":143,"items":148},"Security","Deliver code faster without compromising security",{"config":144},{"href":145,"dataGaName":146,"dataGaLocation":45,"icon":147},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[149,154,159],{"text":150,"config":151},"Application Security Testing",{"href":152,"dataGaName":153,"dataGaLocation":45},"/solutions/application-security-testing/","Application security testing",{"text":155,"config":156},"Software Supply Chain Security",{"href":157,"dataGaLocation":45,"dataGaName":158},"/solutions/supply-chain/","Software supply chain security",{"text":160,"config":161},"Software Compliance",{"href":162,"dataGaName":163,"dataGaLocation":45},"/solutions/software-compliance/","software compliance",{"title":165,"link":166,"items":171},"Measurement",{"config":167},{"icon":168,"href":169,"dataGaName":170,"dataGaLocation":45},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[172,176,180],{"text":173,"config":174},"Visibility & Measurement",{"href":169,"dataGaLocation":45,"dataGaName":175},"Visibility and Measurement",{"text":177,"config":178},"Value Stream Management",{"href":179,"dataGaLocation":45,"dataGaName":177},"/solutions/value-stream-management/",{"text":181,"config":182},"Analytics & Insights",{"href":183,"dataGaLocation":45,"dataGaName":184},"/solutions/analytics-and-insights/","Analytics and insights",{"title":186,"items":187},"GitLab for",[188,193,198],{"text":189,"config":190},"Enterprise",{"href":191,"dataGaLocation":45,"dataGaName":192},"/enterprise/","enterprise",{"text":194,"config":195},"Small Business",{"href":196,"dataGaLocation":45,"dataGaName":197},"/small-business/","small business",{"text":199,"config":200},"Public Sector",{"href":201,"dataGaLocation":45,"dataGaName":202},"/solutions/public-sector/","public sector",{"text":204,"config":205},"Pricing",{"href":206,"dataGaName":207,"dataGaLocation":45,"dataNavLevelOne":207},"/pricing/","pricing",{"text":209,"config":210,"link":212,"lists":216,"feature":300},"Resources",{"dataNavLevelOne":211},"resources",{"text":213,"config":214},"View all resources",{"href":215,"dataGaName":211,"dataGaLocation":45},"/resources/",[217,250,272],{"title":218,"items":219},"Getting started",[220,225,230,235,240,245],{"text":221,"config":222},"Install",{"href":223,"dataGaName":224,"dataGaLocation":45},"/install/","install",{"text":226,"config":227},"Quick start guides",{"href":228,"dataGaName":229,"dataGaLocation":45},"/get-started/","quick setup checklists",{"text":231,"config":232},"Learn",{"href":233,"dataGaLocation":45,"dataGaName":234},"https://university.gitlab.com/","learn",{"text":236,"config":237},"Product documentation",{"href":238,"dataGaName":239,"dataGaLocation":45},"https://docs.gitlab.com/","product documentation",{"text":241,"config":242},"Best practice videos",{"href":243,"dataGaName":244,"dataGaLocation":45},"/getting-started-videos/","best practice videos",{"text":246,"config":247},"Integrations",{"href":248,"dataGaName":249,"dataGaLocation":45},"/integrations/","integrations",{"title":251,"items":252},"Discover",[253,258,262,267],{"text":254,"config":255},"Customer success stories",{"href":256,"dataGaName":257,"dataGaLocation":45},"/customers/","customer success stories",{"text":259,"config":260},"Blog",{"href":261,"dataGaName":5,"dataGaLocation":45},"/blog/",{"text":263,"config":264},"Remote",{"href":265,"dataGaName":266,"dataGaLocation":45},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":268,"config":269},"TeamOps",{"href":270,"dataGaName":271,"dataGaLocation":45},"/teamops/","teamops",{"title":273,"items":274},"Connect",[275,280,285,290,295],{"text":276,"config":277},"GitLab Services",{"href":278,"dataGaName":279,"dataGaLocation":45},"/services/","services",{"text":281,"config":282},"Community",{"href":283,"dataGaName":284,"dataGaLocation":45},"/community/","community",{"text":286,"config":287},"Forum",{"href":288,"dataGaName":289,"dataGaLocation":45},"https://forum.gitlab.com/","forum",{"text":291,"config":292},"Events",{"href":293,"dataGaName":294,"dataGaLocation":45},"/events/","events",{"text":296,"config":297},"Partners",{"href":298,"dataGaName":299,"dataGaLocation":45},"/partners/","partners",{"backgroundColor":301,"textColor":302,"text":303,"image":304,"link":308},"#2f2a6b","#fff","Insights for the future of software development",{"altText":305,"config":306},"the source promo card",{"src":307},"/images/navigation/the-source-promo-card.svg",{"text":309,"config":310},"Read the latest",{"href":311,"dataGaName":312,"dataGaLocation":45},"/the-source/","the source",{"text":314,"config":315,"lists":317},"Company",{"dataNavLevelOne":316},"company",[318],{"items":319},[320,325,331,333,338,343,348,353,358,363,368],{"text":321,"config":322},"About",{"href":323,"dataGaName":324,"dataGaLocation":45},"/company/","about",{"text":326,"config":327,"footerGa":330},"Jobs",{"href":328,"dataGaName":329,"dataGaLocation":45},"/jobs/","jobs",{"dataGaName":329},{"text":291,"config":332},{"href":293,"dataGaName":294,"dataGaLocation":45},{"text":334,"config":335},"Leadership",{"href":336,"dataGaName":337,"dataGaLocation":45},"/company/team/e-group/","leadership",{"text":339,"config":340},"Team",{"href":341,"dataGaName":342,"dataGaLocation":45},"/company/team/","team",{"text":344,"config":345},"Handbook",{"href":346,"dataGaName":347,"dataGaLocation":45},"https://handbook.gitlab.com/","handbook",{"text":349,"config":350},"Investor relations",{"href":351,"dataGaName":352,"dataGaLocation":45},"https://ir.gitlab.com/","investor relations",{"text":354,"config":355},"Trust Center",{"href":356,"dataGaName":357,"dataGaLocation":45},"/security/","trust center",{"text":359,"config":360},"AI Transparency Center",{"href":361,"dataGaName":362,"dataGaLocation":45},"/ai-transparency-center/","ai transparency center",{"text":364,"config":365},"Newsletter",{"href":366,"dataGaName":367,"dataGaLocation":45},"/company/contact/","newsletter",{"text":369,"config":370},"Press",{"href":371,"dataGaName":372,"dataGaLocation":45},"/press/","press",{"text":374,"config":375,"lists":376},"Contact us",{"dataNavLevelOne":316},[377],{"items":378},[379,382,387],{"text":52,"config":380},{"href":54,"dataGaName":381,"dataGaLocation":45},"talk to sales",{"text":383,"config":384},"Get help",{"href":385,"dataGaName":386,"dataGaLocation":45},"/support/","get help",{"text":388,"config":389},"Customer portal",{"href":390,"dataGaName":391,"dataGaLocation":45},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":393,"login":394,"suggestions":401},"Close",{"text":395,"link":396},"To search repositories and projects, login to",{"text":397,"config":398},"gitlab.com",{"href":59,"dataGaName":399,"dataGaLocation":400},"search login","search",{"text":402,"default":403},"Suggestions",[404,406,410,412,416,420],{"text":74,"config":405},{"href":79,"dataGaName":74,"dataGaLocation":400},{"text":407,"config":408},"Code Suggestions (AI)",{"href":409,"dataGaName":407,"dataGaLocation":400},"/solutions/code-suggestions/",{"text":125,"config":411},{"href":127,"dataGaName":125,"dataGaLocation":400},{"text":413,"config":414},"GitLab on AWS",{"href":415,"dataGaName":413,"dataGaLocation":400},"/partners/technology-partners/aws/",{"text":417,"config":418},"GitLab on Google Cloud",{"href":419,"dataGaName":417,"dataGaLocation":400},"/partners/technology-partners/google-cloud-platform/",{"text":421,"config":422},"Why GitLab?",{"href":87,"dataGaName":421,"dataGaLocation":400},{"freeTrial":424,"mobileIcon":429,"desktopIcon":434,"secondaryButton":437},{"text":425,"config":426},"Start free trial",{"href":427,"dataGaName":50,"dataGaLocation":428},"https://gitlab.com/-/trials/new/","nav",{"altText":430,"config":431},"Gitlab Icon",{"src":432,"dataGaName":433,"dataGaLocation":428},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":430,"config":435},{"src":436,"dataGaName":433,"dataGaLocation":428},"/images/brand/gitlab-logo-type.svg",{"text":438,"config":439},"Get Started",{"href":440,"dataGaName":441,"dataGaLocation":428},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":443,"mobileIcon":447,"desktopIcon":449},{"text":444,"config":445},"Learn more about GitLab Duo",{"href":79,"dataGaName":446,"dataGaLocation":428},"gitlab duo",{"altText":430,"config":448},{"src":432,"dataGaName":433,"dataGaLocation":428},{"altText":430,"config":450},{"src":436,"dataGaName":433,"dataGaLocation":428},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":456,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"title":457,"button":458,"image":462,"config":466,"_id":468,"_type":31,"_source":33,"_file":469,"_stem":470,"_extension":36},"/shared/en-us/banner","is now in public beta!",{"text":85,"config":459},{"href":460,"dataGaName":461,"dataGaLocation":45},"/gitlab-duo/agent-platform/","duo banner",{"altText":463,"config":464},"GitLab Duo Agent Platform",{"src":465},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":467},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":472,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":473,"_id":677,"_type":31,"title":678,"_source":33,"_file":679,"_stem":680,"_extension":36},"/shared/en-us/main-footer",{"text":474,"source":475,"edit":481,"contribute":486,"config":491,"items":496,"minimal":669},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":476,"config":477},"View page source",{"href":478,"dataGaName":479,"dataGaLocation":480},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":482,"config":483},"Edit this page",{"href":484,"dataGaName":485,"dataGaLocation":480},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":487,"config":488},"Please contribute",{"href":489,"dataGaName":490,"dataGaLocation":480},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":492,"facebook":493,"youtube":494,"linkedin":495},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[497,520,576,605,639],{"title":63,"links":498,"subMenu":503},[499],{"text":500,"config":501},"DevSecOps platform",{"href":72,"dataGaName":502,"dataGaLocation":480},"devsecops platform",[504],{"title":204,"links":505},[506,510,515],{"text":507,"config":508},"View plans",{"href":206,"dataGaName":509,"dataGaLocation":480},"view plans",{"text":511,"config":512},"Why Premium?",{"href":513,"dataGaName":514,"dataGaLocation":480},"/pricing/premium/","why premium",{"text":516,"config":517},"Why Ultimate?",{"href":518,"dataGaName":519,"dataGaLocation":480},"/pricing/ultimate/","why ultimate",{"title":521,"links":522},"Solutions",[523,528,530,532,537,542,546,549,553,558,560,563,566,571],{"text":524,"config":525},"Digital transformation",{"href":526,"dataGaName":527,"dataGaLocation":480},"/topics/digital-transformation/","digital transformation",{"text":150,"config":529},{"href":152,"dataGaName":150,"dataGaLocation":480},{"text":139,"config":531},{"href":121,"dataGaName":122,"dataGaLocation":480},{"text":533,"config":534},"Agile development",{"href":535,"dataGaName":536,"dataGaLocation":480},"/solutions/agile-delivery/","agile delivery",{"text":538,"config":539},"Cloud transformation",{"href":540,"dataGaName":541,"dataGaLocation":480},"/topics/cloud-native/","cloud transformation",{"text":543,"config":544},"SCM",{"href":135,"dataGaName":545,"dataGaLocation":480},"source code management",{"text":125,"config":547},{"href":127,"dataGaName":548,"dataGaLocation":480},"continuous integration & delivery",{"text":550,"config":551},"Value stream management",{"href":179,"dataGaName":552,"dataGaLocation":480},"value stream management",{"text":554,"config":555},"GitOps",{"href":556,"dataGaName":557,"dataGaLocation":480},"/solutions/gitops/","gitops",{"text":189,"config":559},{"href":191,"dataGaName":192,"dataGaLocation":480},{"text":561,"config":562},"Small business",{"href":196,"dataGaName":197,"dataGaLocation":480},{"text":564,"config":565},"Public sector",{"href":201,"dataGaName":202,"dataGaLocation":480},{"text":567,"config":568},"Education",{"href":569,"dataGaName":570,"dataGaLocation":480},"/solutions/education/","education",{"text":572,"config":573},"Financial services",{"href":574,"dataGaName":575,"dataGaLocation":480},"/solutions/finance/","financial services",{"title":209,"links":577},[578,580,582,584,587,589,591,593,595,597,599,601,603],{"text":221,"config":579},{"href":223,"dataGaName":224,"dataGaLocation":480},{"text":226,"config":581},{"href":228,"dataGaName":229,"dataGaLocation":480},{"text":231,"config":583},{"href":233,"dataGaName":234,"dataGaLocation":480},{"text":236,"config":585},{"href":238,"dataGaName":586,"dataGaLocation":480},"docs",{"text":259,"config":588},{"href":261,"dataGaName":5,"dataGaLocation":480},{"text":254,"config":590},{"href":256,"dataGaName":257,"dataGaLocation":480},{"text":263,"config":592},{"href":265,"dataGaName":266,"dataGaLocation":480},{"text":276,"config":594},{"href":278,"dataGaName":279,"dataGaLocation":480},{"text":268,"config":596},{"href":270,"dataGaName":271,"dataGaLocation":480},{"text":281,"config":598},{"href":283,"dataGaName":284,"dataGaLocation":480},{"text":286,"config":600},{"href":288,"dataGaName":289,"dataGaLocation":480},{"text":291,"config":602},{"href":293,"dataGaName":294,"dataGaLocation":480},{"text":296,"config":604},{"href":298,"dataGaName":299,"dataGaLocation":480},{"title":314,"links":606},[607,609,611,613,615,617,619,623,628,630,632,634],{"text":321,"config":608},{"href":323,"dataGaName":316,"dataGaLocation":480},{"text":326,"config":610},{"href":328,"dataGaName":329,"dataGaLocation":480},{"text":334,"config":612},{"href":336,"dataGaName":337,"dataGaLocation":480},{"text":339,"config":614},{"href":341,"dataGaName":342,"dataGaLocation":480},{"text":344,"config":616},{"href":346,"dataGaName":347,"dataGaLocation":480},{"text":349,"config":618},{"href":351,"dataGaName":352,"dataGaLocation":480},{"text":620,"config":621},"Sustainability",{"href":622,"dataGaName":620,"dataGaLocation":480},"/sustainability/",{"text":624,"config":625},"Diversity, inclusion and belonging (DIB)",{"href":626,"dataGaName":627,"dataGaLocation":480},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":354,"config":629},{"href":356,"dataGaName":357,"dataGaLocation":480},{"text":364,"config":631},{"href":366,"dataGaName":367,"dataGaLocation":480},{"text":369,"config":633},{"href":371,"dataGaName":372,"dataGaLocation":480},{"text":635,"config":636},"Modern Slavery Transparency Statement",{"href":637,"dataGaName":638,"dataGaLocation":480},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":640,"links":641},"Contact Us",[642,645,647,649,654,659,664],{"text":643,"config":644},"Contact an expert",{"href":54,"dataGaName":55,"dataGaLocation":480},{"text":383,"config":646},{"href":385,"dataGaName":386,"dataGaLocation":480},{"text":388,"config":648},{"href":390,"dataGaName":391,"dataGaLocation":480},{"text":650,"config":651},"Status",{"href":652,"dataGaName":653,"dataGaLocation":480},"https://status.gitlab.com/","status",{"text":655,"config":656},"Terms of use",{"href":657,"dataGaName":658,"dataGaLocation":480},"/terms/","terms of use",{"text":660,"config":661},"Privacy statement",{"href":662,"dataGaName":663,"dataGaLocation":480},"/privacy/","privacy statement",{"text":665,"config":666},"Cookie preferences",{"dataGaName":667,"dataGaLocation":480,"id":668,"isOneTrustButton":28},"cookie preferences","ot-sdk-btn",{"items":670},[671,673,675],{"text":655,"config":672},{"href":657,"dataGaName":658,"dataGaLocation":480},{"text":660,"config":674},{"href":662,"dataGaName":663,"dataGaLocation":480},{"text":665,"config":676},{"dataGaName":667,"dataGaLocation":480,"id":668,"isOneTrustButton":28},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[682],{"_path":683,"_dir":684,"_draft":6,"_partial":6,"_locale":7,"content":685,"config":689,"_id":691,"_type":31,"title":18,"_source":33,"_file":692,"_stem":693,"_extension":36},"/en-us/blog/authors/anna-vovchenko","authors",{"name":18,"config":686},{"headshot":687,"ctfId":688},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669159/Blog/Author%20Headshots/anna_vovchenko_headshot.png","4bLGBzB5LA0jYw0y9IqCs2",{"template":690},"BlogAuthor","content:en-us:blog:authors:anna-vovchenko.yml","en-us/blog/authors/anna-vovchenko.yml","en-us/blog/authors/anna-vovchenko",{"_path":695,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"header":696,"eyebrow":697,"blurb":698,"button":699,"secondaryButton":703,"_id":705,"_type":31,"title":706,"_source":33,"_file":707,"_stem":708,"_extension":36},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":47,"config":700},{"href":701,"dataGaName":50,"dataGaLocation":702},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":52,"config":704},{"href":54,"dataGaName":55,"dataGaLocation":702},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1755644109539]