iTranslated by AI
Distributing Cloud Functions Using GitHub Packages Container Registry
As the long title suggests, this is an experimental record of various things I wanted to try out.
Background
I have a Cloud Function (HTTP-triggered) that is called from a certain service. Since I was calling the Cloud Function on GCP even during local execution and testing, I wondered if I could containerize the Cloud Function and start it locally for access. That was the starting point.
Environment
- macOS Catalina (10.15.7)
- Docker Engine (20.10.5)
- Nodejs (14.x)
Building a Cloud Functions Function for Local Startup
First, I had to make the Cloud Functions function requestable via HTTP. Initially, I thought about wrapping it with Express.js to turn it into a web server, but the official documentation mentioned that Cloud Functions actually uses an OSS library called Functions Frameworks.
Furthermore, a tool called pack is provided to build functions into containers that can be run locally. By using this, it automatically creates a container that wraps the function with Functions Frameworks. How convenient!
By the way, I also learned while researching this that the containers for Cloud Functions are placed in GCP's Container Registry and are accessible to users. I was also able to pull and run these locally.
1. Create a function
The function to be created this time is triggered by HTTP. I will use the sample from the official documentation for the function implementation.
First, initialize npm and install the module.
npm init
npm install escape-html --save
Place the sample implementation.
const escapeHtml = require('escape-html');
/**
* HTTP Cloud Function.
*
* @param {Object} req Cloud Function request context.
* More info: https://expressjs.com/en/api.html#req
* @param {Object} res Cloud Function response context.
* More info: https://expressjs.com/en/api.html#res
*/
exports.helloHttp = (req, res) => {
res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
};
2. Build the container with pack
Refer to the pack installation instructions and install it using brew. I actually wanted to run it in a container, but I couldn't due to a docker.sock permission error (it didn't work even with sudo, so I gave up for now).
brew install buildpacks/tap/pack
Following the procedure in the official documentation, build with pack.
pack build \
--builder gcr.io/buildpacks/builder:v1 \
--env GOOGLE_RUNTIME=nodejs \
--env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \
--env GOOGLE_FUNCTION_TARGET=helloHttp \
cloud-functions-sample
3. Run the container
Start the built container. Bind port 8080 to the container's port.
docker run --rm -p 8080:8080 cloud-functions-sample
You can see that the response from the function is returned by accessing localhost:8080!
$ curl http://localhost:8080
Hello World!
I will push the sample function created so far to GitHub.
Build with GitHub Actions and register in GitHub Packages Container Registry
Next, create the GitHub Actions definition.
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the master branch
push:
branches: [master]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
env:
IMAGE_NAME: cloud-functions-sample
strategy:
matrix:
node-version: [14.x]
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Run npm install
run: npm install
- name: Install pack-cli
run: |
sudo add-apt-repository ppa:cncf-buildpacks/pack-cli \
&& sudo apt-get update \
&& sudo apt-get install pack-cli
- name: Run pack build
run: |
pack build \
--builder gcr.io/buildpacks/builder:v1 \
--env GOOGLE_RUNTIME=nodejs \
--env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \
--env GOOGLE_FUNCTION_TARGET=helloHttp \
$IMAGE_NAME
- name: Log in to GitHub Docker Registry
uses: docker/login-action@v1
with:
registry: docker.pkg.github.com
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push a container image to GitHub Container Registory
run: |
docker tag $IMAGE_NAME docker.pkg.github.com/$GITHUB_REPOSITORY/$IMAGE_NAME:latest \
&& docker push docker.pkg.github.com/$GITHUB_REPOSITORY/$IMAGE_NAME:latest
The installation steps for pack also included instructions for Ubuntu, so I was able to install it by simply copying and pasting.
The steps to push to the Container Registry are similar to other registries. For authentication, you use either a GITHUB_TOKEN or a Personal Access Token. In the case of GitHub Actions, a GITHUB_TOKEN is automatically issued, so it's very convenient as you don't need to manage credentials!
Registration of the container to the Container Registry is now complete.
Pull and run from GitHub Packages Container Registry
Finally, let's try to pull and run the registered container locally.
$ docker pull docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample:latest
Error response from daemon: Head https://docker.pkg.github.com/v2/bisque33/github-container-integration/cloud-functions-sample/manifests/latest: no basic auth credentials
It seems that authentication is required even though it's a public repository.
For authentication, generate a Personal access token from the Developer settings in your personal settings. The permission only needs to be read:packages.
I placed the issued token in ~/.github/pat-only-read-packages.txt. Using that, I'll perform a docker login and try docker pull again.
$ cat ~/.github/pat-only-read-packages.txt | docker login https://docker.pkg.github.com -u bisque33 --password-stdin
Login Succeeded
$ docker pull docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample:latest
WARNING: ⚠️ Failed to pull manifest by the resolved digest. This registry does not
appear to conform to the distribution registry specification; falling back to
pull by tag. This fallback is DEPRECATED, and will be removed in a future
release. Please contact admins of https://docker.pkg.github.com. ⚠️
latest: Pulling from bisque33/github-container-integration/cloud-functions-sample
c81a311ac26a: Already exists
05c49db37ee9: Already exists
3c2cba919283: Already exists
91af061cbbcd: Already exists
45d7126bebce: Already exists
cf6b7f7a08d9: Pull complete
09b4558c7e27: Pull complete
8f0d61e10f8d: Pull complete
785ed11a713f: Pull complete
476a9b260057: Pull complete
769b7fb5f768: Pull complete
9e1b79ed05fb: Pull complete
Digest: sha256:1e6d4e80cd55be14300f5cf97955d6749712c2d0feae9284917c27b68ae95981
Status: Downloaded newer image for docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample:latest
docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample:latest
$ docker images | grep cloud-functions-sample
docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample latest dea60fbcdd4b 41 years ago 224MB
A WARNING was displayed, but putting that aside for now, I confirmed it works correctly upon startup!
docker run --rm -p 8080:8080 docker.pkg.github.com/bisque33/github-container-integration/cloud-functions-sample:latest
$ curl http://localhost:8080
Hello World!
Conclusion
The sample repository I created is available here.
By linking GitHub Actions and GitHub Packages Container Registry, you can easily register images to the registry, making it much easier to share private images!
Discussion