Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hello everyone, thanks for joining my session today. I'm really excited
to be here presenting at Conf 42 Cloud native, and today
I'm going to be talking about how you can run multicloud deployments using GitHub
actions. My name is Rob URL and I lead the developer advocacy team
here at Sitecore and I've been writing software for a very long time.
I'm originally from Manchester, back in the UK, but for about the past ten or
eleven years or so I've been based in Melbourne, Australia. You can see a whole
bunch of different social channels on the screen there, so if you've got any questions
or if you just want to say hello, feel free to reach out after this.
So what am I going to be talking about today? Well, I'm going to be
going through a real world project that we've been running in the developer advocacy team
here at Sitecore. And this was a migration project and this
project covers a lot of the sites that we're responsible for as a department.
If we look at how they were hosted previously, we actually had two separate
monolith solutions we were maintaining and we were in the process of migrating
from the first one into the second one. Now having our
sites set up like this, it led to quite a few problems and
I'm sure some of these are going to sound pretty familiar. We had two separate
code bases to maintain, so that meant if we were working on one of the
sites, we had to be sure we were in the correct code base when working
on it. On top of that, the bottom one was open source
and available for our community to learn from while the top one was closed
source. So that different governance model also added a layer of complexity
to the process. One of the biggest problems we had though was there
was a whole heap of infrastructure powering these two completely separate instances.
And I'm sure my manager wasn't all that happy with the reassure bill we were
rebuilding each month. So we were kind of partway through this
migration that we never actually ended up completing. But then what happened internally
was sitecore released a new SaaS version of our CMS and that seemed to
solve a lot of our problems for us. It allowed us to create
a new target state architecture that we were working towards and this would allow us
to unify all of these sites we were responsible for into a single location,
both from a code perspective and from an architecture perspective as well.
And that gave us some great benefits. First of all, it unified
that developer experience, so no longer would we be dealing with two separate
code repositories. We were going to work from a single Monorepo repository
hosting all of the sites that we're working from. And we chose
to operate in the open source model we'd used on the second repository before
because we'd got a lot of great community interactions around that and we wanted to
maintain that going forward. One of the big wins as well was also
the drastically smaller application footprint here because we were
now running off a SaaS CMS backend. We'd offloaded a lot
of the infrastructure into that application instead. So all
we're responsible for now is hosting these heads. It's a headless
CMS. So each of the sites we're building is going to have its own head.
We were going to deploy ourselves, but it gave us a lot of freedom.
Then we could actually choose the best technology to host those heads. Completely different clouds
depending on the head we're deploying and the technology it's built upon.
And this finally would let us completed that migration,
unifying everything into one single approach. So how
did we set this up? Well, as we said, we wanted a Monorepo
containing all of our code, but we still wanted to have the flexibility
of where that code got deployed to. So this was the
kind of high level architecture that we ended up going with.
We'd have the four sites we talked about before we had our mvp site,
which is an ASP. Net six application which we wanted to
push out to an Azure web app being written in. Net.
Azure is a fantastic host for that, but we
had our other three sites which are now built with NextJs. So the
Sukon Anz, the Subcon EU and the Subcon events site.
These were built in next JS and Vercel is a fantastic host for those.
So we wanted to be able to use the best technology for the job.
The top three sites were powered by the new SAS CMS on the
back end, and the bottom ones was the static site. There's no actual content
management functionality in there, it's still in the same repo, but it's just
static content in there. So our users would come along and then they'd
be able to interact with these four different sites regardless of which cloud
they're hosted in. And you may ask why we chose these different clouds.
It mostly came back to the technology choice that we'd made in designing them and
building them. The bottom three sites you see, they're really static
content sites. There isn't much interaction on those. So using a
static generator somewhere like NextJs was a really great fit for that.
The MVP site on the other hand had a lot of interactivity in there,
so we power application process through that. So building that
in. Net was a great fit and it also allowed us just to migrate
the existing code base over, drastically sharing our development time.
So how did we configure the CI CD pipelines to deploy to
all these different cloud architectures? Well, we ended up choosing GitHub actions
to power this and there's a few reasons for this, but mainly
it seems to be aware Microsoft's are pushing a lot of their activities nowadays and
it gave us an opportunity to show our community how you could build out
CI CD pipelines using that technology should they choose to do it as well.
And we went through a few iterations for know much like with most things in
software you kind of never get it right first time.
Our first iteration was to have one big pipeline. So we had
one big YaMl document which basically went and pushed our configuration and
our content out to the SAS CMS XM cloud.
It would then go and deploy the three different Versailles out with
the different targets out of the mono repo and then finally it would also
deploy our MVP site out our. Net application that was
going out to our Azure web app. And it worked.
It did, but it was really heavy. If I'm
making a single line change to one of my JavaScript sites
over here, why am I deploying my. Net site out over here?
That they're not really related, they come from the same Monorepo
repository. But we needed a more targeted deployments approach
that allow us to iterate much more quickly when developers
are committing changes into the repository. So what we ended up doing was
splitting out the pipelines by the target system.
So we have a different pipeline to deploy our changes to our SAS CMS.
We have a different pipeline to deploy changes to the MVP application.
We have a different pipeline to deploy changes to the different subcont applications
and to power that. We created our own custom set of reusable
workflows which are designed to make this more easy.
So let's take a look over in vs code now we'll see how all this
is created. Okay, so I've loaded up the repository here
and what you'll see straight away is you get this
GitHub folder, GitHub and this is where your GitHub actions
are going to exist. And they all exist inside this workflows folder.
And straight away you'll see we have a naming content here. You can see
the files at the top with a build underscore prefix are there
define how to build a certain type of application. So we have
a shared workflow showing how to build a. Net app and we have a
shared workflow describing how to build a nextjs application. If we
hop down to the bottom next you'll see we have ones prefixed with a deploy
underscore name. These ones are all designed to deploy to a specific
cloud platform. So we have a reusable workflow
that will deploy an asset to an Azure web app.
We have a reusable workflow that will deploy an asset out of Versailles.
And finally we have a reusable workflow that will deploy an asset out of our
XM cloud, our SaaS CMS. Where all
the action really happens though is that middle section, the CI CD
prefixed pipelines. These are the ones that actually handle
taking a commit from a repository, building an asset using the
build underscore prefix workflows, and then
deployments it using a deploy underscore prefix workflow.
So let's start to take a look at how these work. I'm going to start
off with the MVP one first. Remember the sites here is hosted in
Azure. And if you think back what I said before,
remember we had that one big pipeline that was triggering all the time
for every commit deploying all these sites, all these assets,
complete overkill. We needed to make this far more targeted.
And you can do that in the top section here of your pipeline definition.
So here's where we define when this specific pipeline is going to
run. And what we do here is we basically set a set of
paths within the repository that we want to monitor. And we're saying
anytime anything changes in a commit in these paths,
we want to execute this pipeline. And this is all in
the on section up here. So you can see we have our push section
and we have a pull request section. So whenever anything
changes, either for these definitions by itself. So when anyone
changes my build definition we're looking at here. This leverages the
build net YaMl. So whenever anyone changes the build net YAml,
or when anyone changes the deploy Azure web app Yaml, we also
need to rerun this. We need to test those changes are correct inside
the source folder we also have a series of subfolders
all within rendering folders internally. That's where
all the source code split up for this mvp application. So we're going to monitor
all of those. And if any changes happen in any of those folders,
again we're going to trigger this workflow.
You might notice we also have a workflow dispatch trigger on
here. All that means is that I can log into GitHub into the
GitHub actions section. I'm going to show you shortly and I'll get a button that
allows me to manually trigger a deployment. If you don't have that
included, the only way to trigger Deployment will be through a
push or a pull request in this case. So once
we've defined when this is going to run, then we start to define what
jobs we're actually going to execute. And this is very similar to most
standard YAML based deployment pipelines you'll have seen in a lot of other systems.
So we have our jobs download and then within there we define each
of the jobs we're going to run. So we have our first job
to build our net application. Then after that we're going to
deploy to our staging site. And then finally after that we're
going to deploy out to our production site. So let's start with our build
net definition. You can see it
pulls in the build net YAMl from the same location here
and we're passing in the build config. This is a parameter we pass in.
We want to build this in release mode.
If we load up the build net YAml, you can see this is fairly straightforward.
This is all just built out using MS build.
We do have some old. Net framework code in there, which is why we've gone
that way instead of the CLI. But you can see here
you just end up with a step based action. So we start out by checking
out the repository. We're going to basically install CMS
build into our build runner. We install Nuget.
We run a Nuget restore on our solution and
then finally we're going to built the solution itself. Remember, we passed in the build
configuration before allowing us to define that. This is going to be executed in release
mode. So it's fairly straightforward. And what you get at the
end of that is an asset which is basically the build output
of your site. So once that's completed
we can then move to the deployment stage. And as I said, we're going to
deploy to staging site first, and then after that we're
going to deploy out to our production site. So here's where we
start to do that. We're again using this referenced reusable
workflow, this time the deploy Azure web app workflow.
And what we're saying here is this needs net. So here's how we're defining the
ordering of these jobs. So this will only execute after the
build net has executed.
Then we're going to basically set some parameters again.
So we're
going to deploy in debug mode here. And that's because it's the staging sites.
So we want different sites of logging to appear here. We want people
to be able to debug easier in sharing than they do in production.
We're going to choose the project itself we're deploying
and then we're going to create the asset name and the web app name we're
deploying to. Finally, we also pass in the secrets. We have some secrets
here stored in GitHub and this is the publishing profile information,
basically showing how to physically publish into that Azure web app.
If we take a look at this deploy Azure web app shared
pipeline here, you can see we first of all define all those parameters.
So the build config, the project location, the asset name and the
web app name. This one's a little simpler. In here
we're just deploying net six code so we can use the net CLI.
It becomes a lot easier. You can see we basically set this to run on
a Windows machine. We start again by checking
out the source code. After this we set the version of
Net we're going to run so version six and we can then run a build
and publish. So we CD into where the project location is.
We run a. Net restore, we run a. Net build for the build configuration
passed in, and then we run a. Net publish. And this is going to publish
to a specific location inside of that built runner.
So inside that Windows Runner and you can see the output param here and the
location we've passed in with the asset name.
Finally after that's completed, we could do a deployment.
So we could take this deployment here and we can use
the secrets that are passed in the publishing profile and publish that out
to an actual web app. If we go and take a look
back at our main CI CD pipeline, once that's completed,
we'll then go to production. And you'll notice we have an if
clause here and this basically only gets executed if it's
running against main. So what that means is that when someone
opens a PR against this repository, it'll build
the net code first. So that steps there just to make sure everything's correct.
Any source code changes are syntactically right and the solution still builds.
After that we'll deploy to staging. Every commit always goes out to sharing.
That's always where we need to test our code. But if we're just in a
PR state, the GitHub reference won't be for Maino be for that branch.
So we just stop there. Basically it'll just go to staging and allow people
to test. Once that PR is merged into main,
then this will execute again and it'll go all the way out to prod,
running the exact same steps as before. But this time we're going to run in
release mode because we need this to go out to production.
Okay, so let's take a look at another example of these. Let's take
a look at one of the Versaillesites.
Okay, so I'm going to load up the Sukon AnZ definition here. This is
a site that's built with next js and is going to be deployments out to
Versaille. And again at the start you can see we have this same
definition showing when we want to execute this. So we start off with a
workflow dispatch which gives us the button in GitHub actions,
allowing us to manually trigger a build. But then again we're monitoring pushes
and pull requests. We monitor the three workflow
definitions that this deployments is based on. So that's
the config file we're looking at the CI CD for subcons. And this also
leverages the builddexjs and deploy Versailles pipelines as
well. On top of that, we need to monitor the source code
for this site, and that's all located within this path, within the repository here.
So if any of those assets change, that's when this pipeline is
going to execute. And what does it do? Well, we start
off a game by building the sites. We build the source code for
the site to test that any code changes are correct and that
we can move on to the deployment stage, if that's right. So if we take
a look in the build next JS yaml in
here, we can see we basically pass in the working directory
because we have multiple next JS sites. So we need to pass in which sites
it is we're building. And then we can run this on some really fast
Ubuntu runners, really lightweight. We set some variable,
we set some environment variables we need, and then we pass
in things like the working directory as the parameter. Then we can start to
execute. So we do a checkout of the code base,
we choose which version of node we're working with.
Then we run an NPM install to set up all the dependencies.
We run an NPM build to check that the source code builds,
and then we run an NPM lint to check that it's sync tactically correct
and matches our development guidelines.
If we hop back. Once that's completed successfully, we then
go on to the deployment phase. And you'll notice here we only have one deployment
phase, which is for preview and production. And that's because Vercel works
a little differently than Azure. In Azure we have two different
web apps. We have a staging web app and a production web app, which is
what we saw before for our MVP site,
infracell. How it handles it is the CLI which we'll look
at in a sec, basically has a prod flag which is
passed in, which is how you define whether you're doing a production
or a preview build, basically. So all
of that's handled inside this deploy Versel yaml. So what we do
is we basically reference that one of our shared workflows we created,
and then we pass in a bunch of secrets. So the versel token,
our organization id and the project id for this site
itself. Specifically, if we look in the deploy Versailles
package here, here you can see again we're using these Ubuntu runners.
We do a checkout, we set up node with the version that
we're running on, and then afterwards we're going to use this
aimon net versaille action. And this is one of the really nice things
about GitHub actions. We haven't created this package. This is created
by a member of the GitHub Actions community, and that allows us to automate
deployments to Versailles. And there's thousands upon thousands
of these packages out there, regardless of which language you're building,
with, which cloud provider you're deploying for, there's a whole wealth of these libraries
that you can build your scripts on top of. And if there isn't one,
you can actually build your own and deploy for other people to use as well.
So it's a really nice way of sharing code and deployment strategies
with the community. We then basically pass in all the secrets
we set. So our token, our id, our project id, and then we
have our arguments. So here you can see we're going to pass the prod
flag whenever we're merging into main,
basically. So this is a merge into main, then we want to pass
that flag in, and that is how we
automate deployments out to a versel site. I'm not going to go through all of
the different Versailles. The advantage of having these implemented via shared
templates means that their definition is pretty much the same across anyway,
just with different secrets and different values.
So let's hop over to the browser now, and I'm going to show you how
this actually looks inside of GitHub itself. If we go over
to the actions tab here, we can see all of the different
deployments that have actually been executed. The nice
thing here is on the left you get to see all the different pipelines we
defined before. So it even shows you the shared pipelines
here. Now they're never actually going to be executed themselves, but the ones inside
are. So you can now go and click on one of these, for example,
the MVP site, and it'll then just show deployments to the MVP sites.
Here's where you can see it says this workflow has a workflow dispatch
trigger. So if we wanted to manually trigger this workflow, we could go and
do that ourselves. Now if we go back to the all workflows
view though, here you can see this is a deployment of the CI
CD MVP site. Here's a deployment of the CI CD subcon
EU site. So you can see it's really easy to see
which assets have been deployed. On top of that we can see
which branches have gone out. So here is a deployment all the way out to
main for the subcontinent use site. Here's a deployment
for XM cloud. This is deploying changes to our cloud cms
and that's from a branch. So this will only be going out to our
staging instance. It won't be going all the way to production ready for people to
review those changes. If we take a look at one of these, we can
see how it looks inside. So here's an example of an
MVP change that went all the way out to production. So we can
go and we can see where we've built the net solution. And these are the
same steps that we looked at before. We can see how we set
up the MS build. We installed Nuget. We ran a Nuget restore
for our packages. We ran a build itself. And you can go and
expand these and view detailed log files for all of them,
showing exactly how that step executed and whether it
was successful or not. After that, we went and did our deployments to
our sharing site. So here we set up net, we ran
our build and publish. So again, you're getting the full output from the CLI here.
And then we took that asset and we published it out to our specific
Azure web app. The same then happens for production as
well. Here you can see we set up net the version that
we needed to install into the runner. We then ran a
build and publish and then
we deployed it out to our Azure web app. So you can
see how we have a single repository here we have four
different sites, source code, it's all deploying
to different cloud providers, different instances, all from
a single CI CD instance. So this gives you a really nice
single source of truth for all of your deployments.
Okay, so I just want to finish up with a few conclusions here.
GitHub actions itself as a platform is super flexible.
You get a lot of pipelines out of the box,
allowing you to do a lot of common tasks with common frameworks.
But if you're working with a framework or a hosting provider
where it isn't that common, there isn't something out of the box. You can see
what community pipelines are available as well, like for example with the Versailles
CLI wrapper that we used. On top of that, if you find
something that hasn't already been automated by someone, you can create a pipeline
yourself and you can share that for other people to benefit from. It's a really
nice community driven CI CD platform.
On top of that code reuse in your templates matters.
The ability to use shared templates within these YAML definitions
hugely reduced the actual amount of code that we wrote to deploy each of these
sites. If we couldn't have done that, our three different Versailles
sites when they got deployed would each have had to have duplicated each of that
build code, each of that deploy code. And if we change that down the track,
then we'd have to go and change it in each of those definitions, which is
not that nicer way of working. The ability to create these shared templates
makes it a much more nicer way of building out reuse into
your CI CD pipelines. Finally,
if you want to see all this in action, as I mentioned at the start,
this repository is open source. We're still a work in
progress. There's still jobs for us to do on this.
You will have noticed, I think throughout this that there's actually multiple builds happening
throughout. Next on my list is to share the build asset when we
first do our build in our first step with the later steps to try and
get our build time down. But yeah, you can go and check out a repository
today and see how we're getting along. So thanks a lot for listening to
my talk today. Again, you can see all my different social handles there on
the screen. So if you've got any questions or just want to reach out and
say hello, please get in touch. And apart from that, thanks for listening.