This blog post will describe how to create CI/CD pipeline with Oracle Cloud Infrastructure. The deployment environment for this is OCI Functions, which is a serverless application.
The scenario we build is as follows;
- Developer commit code to local repository
- Code push to github repository
- Code get synced to OCI code repository
- Run OCI Continuous Integration (CI)
- Container image will be uploaded to container registry
- Automatically trigger OCI Continuous Delivery (CD) and deploy to OCI Functions
- Use OCI Gateway to access the function from public Internet
Architecture
Figure 1 shows the complete architecture of the scenario.
Figure 1: Architecture
Activities
The following are steps to achieve our setup and will explain in detail in the next section.
- Create DevOps Dynamic group
- Create Policies for DevOps Project
- Create Container Registry Repository
- Verify code and github
- Create DevOps Project
- Create External Connection to github
- Create Build Pipeline and test
- Create Function and deploy application
- Create API Gateway
- Create Deployment Pipeline
- Test the CI/CD pipeline
Step-by-Step Guide
This section will explain the tasks along with the necessary screenshots of each activity.
1. Create DevOps Dynamic group
The dynamic group facilitates the grouping of multiple resources, so it’s convenient to write policies. We need to create a dynamic group for DevOps project at the start.
Figure 2: Locate Dynamic Group
Note down the OCID of the compartment to include it in the dynamic group rule.
Figure 3: Create Dynamic group
This rule requires it to be included in the dynamic group. It covers all resource types needed for our project.
“All {resource.compartment.id = ‘ocid1.compartment.oc1..xxxxxx’, Any {resource.type = ‘devopsdeploypipeline’, resource.type = ‘devopsbuildpipeline’, resource.type = ‘devopsrepository’, resource.type = ‘devopsconnection’, resource.type = ‘devopstrigger’}}”
2. Create Policies for DevOps Project
The policies are required to control the access to the OCI resources. This can be designed as per the security requirement of the organization. Below is just an example of rights.
Figure 4: Locate Policies
In order to create the policies we need to know the compartment name that our DevOps project is going to create and the dynamic group name that we created in step 1.
Figure 5: Create Policy
Below are a sample set of policies that you can organize your security. You may not require all the policies and may include additional as required.
Allow dynamic-group <dynamic group name> to manage devops-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage devops-repository in compartment <compartment name>
Allow dynamic-group <dynamic group name> to read secret-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use ons-topics in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use adm-knowledge-bases in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage devops-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage adm-vulnerability-audits in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use subnets in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use vnics in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use network-security-groups in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use cabundles in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use vaults in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use keys in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage secret-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage generic-artifacts in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage repos in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage generic-artifacts in compartment <compartment name>
Allow dynamic-group <dynamic group name> to manage devops-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to use ons-topics in compartment <compartment name>
Allow dynamic-group <dynamic group name> to read secret-family in compartment <compartment name>
Allow dynamic-group <dynamic group name> to read devops-family in compartment <compartment name>
3. Create Container Registry Repository
We are going to dockerize our code at the CI (continuous Integration) and store it in the container repository, so that the CD (Continuous Delivery) stage can get it from the repository.
Therefore, we need to create a repository first that can be found at ‘Developer Services’.
Figure 6: Create Repository
4. Verify Code and github repository
In this section, we’ll explore the files required for the deployment. The target is to deploy a simple web page.
The figure 7 shows the build specification file will use for OCI DevOps (CI). If you need more information about building build_spec.yaml please refer to the documentation here.
Make sure to adjust the DOCKER_TAG and REGISTRY with the correct path. The namespace can be found on the container repository created in step 3.
Figure 7: build_spec.yaml
Figure 8 is the ‘Dockerfile’ that will dockerize the web file.
Figure 8: Dockerfile
Figure 9, is the PHP file to deploy. This will have a background colour of blue and version. In the next deployment we can change them and see the execution of CI/CD.
Figure 10 is a configuration file for the API gateway. Please update the ‘functionId’ with the OCID of the function after creating the same.
Figure 11, is representation of our code in the github code repository.
5. Create DevOps Project
We need to create a top and a subscription as prerequisites to create the DevOps project. This is to inform the progress/ status of DevOps project actions.
The OCI DevOps project can be found at ‘Developer Services’.
We can provide a preferred name for the project and choose the previously created topic for the communication.
The Devops project will look like figure 15 after creation. We can enable logging as the first activity.
6. Create External Connection to github
Since our objective is to sync our code in github with our code repository in the DevOps project we need to create an ‘external connection’. This will make sure to sync the committed code from github to DevOps project in OCI in the setup time interval.
In order to do that, we first need to generate a token at github and store it in OCI securely. OCI DevOps project can then create connections using the credentials.
Access github – > Settings -> Developer Settings -> Fine-grained tokens
Provide a name for the token and generate one.There you can decide the permission levels as you preferred.
Then we need to copy the token generated.
If you don’t have a OCI vault already, you need to create one to store the token.
And then, create a master encryption key as in figure 20.
Then it is time to store the password (github token) as a secret.
We can now create an external connection at the OCI DevOps project. Choose the type as ‘github’ and previously created secret from the menu.
We can then validate the connection by clicking ‘Validate connection’.
Next step is to bring our code in github and have a repository in DevOps project. We need to navigate to ‘Code Repositories’ and click on ‘Mirror repository’.
As in figure 24, we can provide connection name and schedule time for the connection. With the default setting, it automatically syncs with the github repository every 15 minutes. We can adjust it to lower or higher.
7. Create Build Pipeline and Test
We can create a build pipeline which is the continuous integration (CI) part now. This is available under the resources section of the DevOps project.
Once you click on the ‘create’ you can see a graphical user interface like in figure 26. From here, we can add stages to the pipeline.
In the build pipeline, ‘Managed Build’ needs to be the first stage. This will dockerize the application for the rest of stages.
After providing a name for the stage, we need to add the repository that contains the code. In our case it’s from ‘OCI Code Repository’. Actually we could use github directly as the code repository as well. This is just to show the options we have. We need to provide ‘build_spec.yaml’ file path if it doesn’t include in the root path.
Once the managed build stage dockerize the application, we need to upload it to the container registry repository. This can be achieved via the ‘Deliver artifacts’ stage.
Since we haven’t created a ‘artifact’ with container registry path, we can create one here. The syntax is as follows;
<region-key>.ocir.io/<tenancy-namespace>/<repo-name>:<tag>
At the configuration step, we need to provide ‘Build config/result artifact name’. This needs to match with ‘outputArtifacts’ name in the ‘build_spec.yaml’ file.
Figure 32 shows the snippet of ‘outputArtifacts’ from the ‘build_spec.yaml’ file.
Now, the build pipeline is ready for dockerize and uploading the image to the container repository. We can test the execution by clicking the ‘Start manual run’ button.
As in figure 34, we can see the successful completion in a few minutes time. We can see the granular level steps in middle section and logs on the right hand side.
The image now appears in the container registry as expected. The deployment pipeline we are setting up next will get this image for the deployment.
8. Create Function and deploy application
Since the image is now uploaded, we can create an OCI function using that. OCI Functions is located under ‘Developer Services’. It’s required to have a VCN and subnet prior to create the function.
As in figure 37, choose ‘Create from existing image’ option and provide the path to the image in container registry repository.
The OCI function is our deployment environment. Therefore we can go back to DevOps project and update this information.
We can provide the details of the function and click ‘Create environment’ to complete the task.
Now we can update the ‘spec.json’ with the correct OCID of the OCI function we created.
9. Create API Gateway
Now we can create an API gateway which allows users to access the application over the Internet.
Firstly we need to create dynamic group as in figure 40.
The rule of the dynamic group is as follows. Make sure to update the compartment OCID correctly.
All {resource.type = ‘ApiGateway’, resource.compartment.id = ‘ocid1.compartment.oc1..xxxx’}
Then we need to create a policy (or update previous policy) with the following two. First policy will require the dynamic group name we just created and the second one requires the user group of yours.
allow dynamic-group <API GW dynamic group> to use functions-family in compartment <compartment Name>
allow group <user group> to use fn-invocation in compartment <compartment Name>
The API Gateway is again available under ‘Developer Services’ and can be created under a public subnet.
Under the resources section of the API gateway, we can find the ‘Deployments’. Here we can provide a name and path (in this case ‘/v1’).
As in figure 43, we can provide authentication (or no authentication in this case), route path (/hello) and backend type (Oracle functions) and create the deployment.
10. Create Deployment Pipeline
Now we can come back to the DevOps project to complete the CI/CD pipeline. We can now start build the ‘Deployment Pipeline’ which takes care of deploying the image to the OCI functions.
Choose the ‘Function’ as the deployment type as in figure 45.
Upon selecting the environment we created earlier, you can see the details get auto populated.
This concludes the deployment pipeline of our DevOps project. Now we need to integrate the ‘Build pipeline’ so that it will flow continuously.
In order to do that, revisit the deployment pipeline and add a stage. This time choose ‘Trigger Deployment’.
In the configure page, choose the deployment pipeline we created earlier. This will auto-poulate the details.
Figure 49 shows the both build and deployment pipeline steps we created. The steps selected are personal choice for the flow and you can mix and match many combinations.
We can now go to the build pipeline and run it by clicking the ‘Start manual run’.
As in figure 51, we can now see both the build pipeline and deployment pipelines ran successfully.
11. Test the CI/CD pipeline
Let’s test the entire cycle and see whether the CI/CD pipeline is working fine. As you may remember, we added an API gateway to expose our application publicly over the Internet. The API gateway has an ‘endpoint’ which we can find in the API gateway page.
We configured the path as (/hello) while creating the setup. Therefore, we need to copy the API endpoint on the web browser and add /hello at the end.
Now we should see the output as in figure 53. Note that version 1 appears in the blue background. This means CI/CD pipeline successfully deployed on the OCI functions.
Now, let’s change the code in hello.php and run the CI/CD pipeline to see whether the entire cycle is working.
We’ll change the version to 2 and the background colour to ‘green’.
After saving the file, let’s commit the changes. Figure 55 shows using the git desktop tool but it can be generic ‘git commit’ as well.
Then we can push the changes to the git repository.
When we create the OCI repository in OCI Devops project, we choose the default sync timing which is 15 minutes. If we want we can sync it manually as well.
Now, go to the build pipeline and run it again.
We can observe the progress of the build pipeline and deploy the pipeline while it’s running. Once it’s completed we can check the browser again for the change. As we can see now the version 2 is appeared correctly along with the new background colour ‘green’.
This concludes the step-by-step guideline of building CI/CD using OCI DevOps and deploy code on OCI Functions.