The Evolution of CI/CD
Author: Martin Li Causi from Azumo
Assessing the Effectiveness of Your CI/CD Strategy
While CI/CD garners much attention for its theoretical advantages, its practical implementation often involves tools or processes operating in isolation from the final product being developed by engineers.
How can we enhance our CI/CD strategy? Often overlooked is the consideration of CI/CD strategy design with the same rigor as application design or scalability planning. What factors contribute to the definition of a robust CI/CD strategy?
One crucial aspect is performance, impacting software engineers significantly. Lengthy continuous integration or deployment durations translate to prolonged wait times for developers seeking bug feedback or user deployment.
Another key factor is the flexibility inherent in our CI/CD processes. By flexibility, I refer to the ease of extending these processes swiftly and securely, minimizing the need for extensive rework. Additionally, it's vital to assess how well our strategy aligns with our chosen CI/CD provider. While many opt for service-based solutions like Github Actions, CircleCI, or cloud providers such as AWS, Azure, or GCP, there's a need to question the rigidity imposed by these technologies. It's also pertinent to explore how seamlessly these solutions can be replicated in a local environment, mirroring remote practices. Discrepancies between local and remote processes are common within organizations, posing challenges to consistency.
Cost efficiency is another critical consideration. Historically, this was less significant due to predominantly stateful environments, where continuous uptime ensured cached dependencies and artifacts streamlined processes. However, advancements in containerization, web assembly, and the adoption of scale-to-zero strategies have introduced complexities, slowing compilation and deployment times and subsequently inflating costs.
Strategically, how much of our CI/CD logic is integrated into the product itself? Traditionally treated as an add-on, CI/CD processes possess inherent logic, from compiling applications with specific parameters to orchestrating artifact uploads and conducting integration tests. Embedding CI/CD processes within our product architecture can significantly enhance our overall CI/CD strategy.
Current State of CI/CD Systems
The current state of the system is characterized by its configuration-based, tightly integrated relationship with the provider. While this configuration provides close integration with the provider's resources, it also tends to segregate the system from its final product. Optimizing this system becomes complex due to its environment dependency and limited local execution capabilities. In summary, the current state reflects a configured environment that presents challenges for optimization and independent local execution:
CI/CD-Pipelines with Dagger.io
… and escape YAML-hell along the way
Build and CI/CD systems are powerful tools that complement each other well.
Systems like Gradle or Maven help us move from code to running software artifacts. But if we move into a highly collaborative setting, the need for a central CI/CD system will emerge soon. Scalable and reproducible build environments for a team are just too helpful to ignore.
Sadly, testing and developing complex pipelines often turns out to be tedious because we have to wait for the central system to pick up and process our jobs. Wouldn’t it be fabulous if we could develop and run our pipeline steps locally without modifying them? This is exactly where dagger.io is coming into play.
Through the smart combination of using containers to isolate build steps and cueing for expressive, declarative, and type-safe configuration of our pipeline, dagger.io creates a flexible build system that—thanks to containers—can be extended in different programming languages.
The project Dagger, created by Solomon Hykes , co-founder and former CTO of Docker, solves the problem. Just like Docker helps you bridge the gap between your development and production environment by packaging your logic into Docker images, Dagger does the same for your pipelines by running your workflow within containers.
Dagger is not just about closing the gap between local and remote, it is also about abstracting away from the language of your CI/CD pipelines. You can still use your favorite CI/CD tool but define the logic of the pipeline using the language of your preference (Dagger supports Node.js, Go, and Python).
How Does It Work?
Which SDK Should I Use?
If you are……
I find myself more at ease working with Node.js, in my case.
Basic Example with Dagger
Step 1: Install the Dagger Node.js SDK
npm install @dagger.io/dagger@latest --save-dev
Step 2: Create a Dagger Client in Node.js
In your project directory, create a new file named build.js and add the following code to it.
领英推荐
/**
* Developer: Esteban Martin Li Causi
* Company: AZUMO LLC
*/
;(async function () {
// initialize Dagger client
let connect = (await import("@dagger.io/dagger")).connect
connect(
async (client) => {
// get Node image
// get Node version
const node = client.container().from("node:16").withExec(["node", "-v"])
// execute
const version = await node.stdout()
// print output
console.log("Hello from Dagger and Node " + version)
},
{ LogOutput: process.stderr }
)
})()
This Node.js stub imports the Dagger SDK and defines an asynchronous function. This function performs the following operations:
Run the Node.js CI tool by executing the command below from the project directory:
Node build.js
As Dagger uses Docker, if we try to execute this test again, it should execute faster because it’s using a cache
Step 3: Test Against a Single Node.js Version
Replace the build.js file from the previous step with the version below
/**
* Developer: Esteban Martin Li Causi
* Company: AZUMO LLC
*/
;(async function () {
// initialize Dagger client
let connect = (await import("@dagger.io/dagger")).connect
connect(
async (client) => {
// get reference to the local project
const source = client
.host()
.directory(".", { exclude: ["node_modules/"] })
// get Node image
const node = client.container().from("node:16")
// mount cloned repository into Node image
const runner = node
.withDirectory("/src", source)
.withWorkdir("/src")
.withExec(["npm", "install"])
// run tests
await runner.withExec(["npm", "test", "--", "--watchAll=false"]).sync()
// build application
// write the build output to the host
await runner
.withExec(["npm", "run", "build"])
.directory("build/")
.export("./build")
},
{ LogOutput: process.stderr }
)
})()
The revised code now does the following:
Run the Node.js CI tool by executing the command below:
dagger run node build.js
Step 4: Test Against Multiple Node.js Versions
/**
* Developer: Esteban Martin Li Causi
* Company: AZUMO LLC
*/
;(async function () {
// initialize Dagger client
let connect = (await import("@dagger.io/dagger")).connect
connect(
async (client) => {
// Set Node versions against which to test and build
const nodeVersions = ["14", "16", "18"]
// get reference to the local project
const source = client
.host()
.directory(".", { exclude: ["node_modules/"] })
// for each Node version
for (const nodeVersion of nodeVersions) {
// get Node image
const node = client.container().from(`node:${nodeVersion}`)
// mount cloned repository into Node image
const runner = node
.withDirectory("/src", source)
.withWorkdir("/src")
.withExec(["npm", "install"])
// run tests
await runner.withExec(["npm", "test", "--", "--watchAll=false"]).sync()
// build application using specified Node version
// write the build output to the host
await runner
.withExec(["npm", "run", "build"])
.directory("build/")
.export(`./build-node-${nodeVersion}`)
}
},
{ LogOutput: process.stderr }
)
})()
This version of the CI tool has additional support for testing and building against multiple Node.js versions.
Run the Node.js CI tool by executing the command below:
dagger run node build.js
Conclusion
In essence, Dagger empowers developers with a flexible, consistent, and platform-agnostic approach to running CI/CD pipelines:
References
Project Manager at Acutrans
6 个月Congrats Licau!