惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
V
V2EX
爱范儿
爱范儿
Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
Check Point Blog
罗磊的独立博客
A
About on SuperTechFans
MyScale Blog
MyScale Blog
S
Security @ Cisco Blogs
博客园 - 聂微东
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
云风的 BLOG
云风的 BLOG
U
Unit 42
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
Security Latest
Security Latest
Y
Y Combinator Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The Hacker News
The Hacker News
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
AWS News Blog
AWS News Blog
NISL@THU
NISL@THU
美团技术团队
S
Securelist
P
Privacy International News Feed
T
Tenable Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog RSS Feed
W
WeLiveSecurity
A
Arctic Wolf
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google DeepMind News
Google DeepMind News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
N
Netflix TechBlog - Medium
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术

Learn Cloud Native

Agentgateway rate limiting for agents | Learn Cloud Native Local development with coding agents on Kubernetes using Signadot | Learn Cloud Native cuenv: one typed file for your whole project | Learn Cloud Native Preflight: AI Code Review Before You Push Anatomy of AI Agents Accessing Google Drive from Next.js Deploying to Fly.io using Dagger and Github Top Cloud-Native & Kubernetes Certifications [2026 Guide] Rapid microservices development with Signadot How to prepare for Istio certified associate exam (ICA) Global Rate Limiting in Istio with Envoy Rate Limit Service My Journey with Istio: From Incubation to Graduation Cilium Network Policy Tutorial: Secure Kubernetes Step by Step Kubernetes Networking: How kube-proxy and iptables Work Istio ServiceEntry: DNS vs. STATIC Resolution & Endpoints Explained Apply an Istio DestinationRule Globally (Mesh-Wide) Istio Rate Limiting: Configure a Local Rate Limiter in Envoy How to expose custom ports on Istio ingress gateway Portainer Tutorial: A Web UI for Kubernetes & Containers Traefik Proxy 2.x and TLS 101 Kubernetes CLI (kubectl) tips you didn't know about Setting up SSL certificates with Istio Gateway ArgoCD Best Practices You Should Know 在 OCI Ampere A1 计算实例上运行 AI Running AI On OCI Ampere A1 Instance How to Deploy Traefik Proxy Using Flux and GitOps Principles Firebase Emulators with Next.js: Local Setup Guide Running Hugo on free Ampere VM (Oracle Cloud Infrastructure) How to use kwatch to detect crashes in Kubernetes clusters Continuous profiling in Kubernetes using Pyroscope Monitoring containers with cAdvisor Creating a Kubernetes cluster in Google Cloud (LAB) Your first Kubernetes Pod and ReplicaSet (LABS) Container Lifecycle Hooks Maybe Convert Wasm Extension Config? GetIstio - CLI, training, and community Attach multiple VirtualServices to Istio Gateway Kubernetes Volumes Explained: Keep Data Beyond the Pod Send a Slack message when Docker images are updated Kubernetes Network Policy Ambassador Container Pattern Start Kubernetes Release Sidecar Container Pattern Kubernetes Init Containers Deploying multiple Istio Ingress Gateways Branch by Abstraction Pattern The Strangler Pattern Kubernetes Development Environment with Skaffold Securing Kubernetes Ingress with Ambassador and Let's Encrypt All About the Ingress Resource How to quarantine Kubernetes pods? Getting started with Kubernetes Horizontal partitioning in MongoDB Docker image tagging scheme Six things to keep in mind when working with Dockerfiles Beginners guide to Docker Beginners guide to gateways and proxies Deploy and Operate Multiple Istio Meshes in one Kubernetes Cluster Managing service meshes with Meshery Circuit Breaking in Istio Explained Kubernetes and Istio service mesh workshop materials Build Netlify-like deployment for React app using Kubernetes pods Six exciting enhancements in Istio 1.4.0 Fallacies of Distributed Systems CAP Theorem Explained Master the Kubernetes CLI (kubectl) - Cheatsheet Minikube Basics and How to Get Started with Kubernetes 5 Tips to Be More Productive with Kubernetes What are sticky sessions and how to configure them with Istio? Debugging Kubernetes applications using Istio Kubernetes Ingress and Istio Gateway Resource Zero Downtime Releases using Kubernetes and Istio Traffic Mirroring with Istio Service Mesh Expose a Kubernetes service on your own custom domain
Build and push your Docker images using Github Actions
Peter Jausovec · 2020-02-20 · via Learn Cloud Native

I was working on a CI (continuous integration) portion for a project that lives in a mono-repo and hosts multiple, independently deployable services. Each service in the repository is versioned separately. There is a VERSION file with the version number inside each service folder. However, the application itself is currently deployed as a monolith - a single, versioned Helm chart with sub-charts for each of the services. Theoretically, we are in the spot where we could be deploying all these services separately by replacing one helm install/upgrade command with a Helm command for each service.

Here's how the project structure looks like:

src
├── service1
│   ├── Dockerfile
│   └── VERSION
└── service2
    ├── Dockerfile
    └── VERSION

The first step I did is to figure out what the easiest way would be to build Docker images for each service and then push the images to a Docker image repository. Github Actions were the logical and simplest choice in this case since the source code lives on Github as well.

I have used an existing starter workflow from Github that builds the image, logs into the Docker registry and then pushes the image to the repository. The initial version I came up with after removing a couple of steps from the original workflow looked like this:

name: Docker

on:
  push:
    branches:
      - master

  env:
    # TODO: Change variable to your image's name.
    IMAGE_NAME: image

jobs:
  push:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Build image
        run: docker build . --file Dockerfile --tag image

      - name: Log into registry
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ secrets.DOCKER_REGISTRY_URL }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

      - name: Push image
        run: |
          IMAGE_ID=${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_REPOSITORY_NAME }}/$IMAGE_NAME
          # Strip git ref prefix from version
          VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
          # Strip "v" prefix from tag name
          [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
          # Use Docker `latest` tag convention
          [ "$VERSION" == "master" ] && VERSION=latest
          echo IMAGE_ID=$IMAGE_ID
          echo VERSION=$VERSION

          docker tag image $IMAGE_ID:$VERSION
          docker push $IMAGE_ID:$VERSION

I added the following Github Secrets, so I don't need to hardcode them inside the workflow (you probably don't want to have your Docker registry password in there):

Github Secret nameDescription
DOCKER_PASSWORDPassword for the Docker registry
DOCKER_USERNAMEUsername for the Docker registry
DOCKER_REGISTRY_URLDocker registry URL (for example: docker.pkg.github.com)
DOCKER_REPOSITORY_NAMERepository name (for example: myrepo)

You could argue that DOCKER_REGISTRY_URL, DOCKER_REPOSITORY_NAME and/or DOCKER_REPOSITORY_NAME don't belong in the secrets and I agree, however it makes it much easier to update them without changing the code. The downside (at least for non-secret variable) is that the values are masked in the logs. For example, when you try to push the Docker image the $IMAGE_ID:$VERSION would show up like this in the logs:

...
The push refers to repository [***/***/service1:0.0.1]
...

Another downside of storing non-secret values as secrets is that you don't get any auditing trail/tracking. Heck, once you set them you can't even see their values. If auditing is something you'd like to have or your workflow hits the Github Secrets limit (max. 100 secrets and up to 65KB in size), you can also store encrypted secrets in your repository and store the password for decrypting them as a Github Secret. Then, whenever your workflow needs access to secrets, you would encrypt them from the repository. You can read more about using encrypted secrets here.

In the above snippet, the version is determined from the tag - since we have multiple services and multiple different versions, I had to change that. So instead of getting github.ref, I had to read the value(s) from the VERSION file:

---
- name: Push image
  run: |
    IMAGE_ID=${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.DOCKER_REPOSITORY_NAME }}/$IMAGE_NAME

    VERSION=$(cat service1/VERSION)

    echo IMAGE_ID=$IMAGE_ID
    echo VERSION=$VERSION

    docker tag image $IMAGE_ID:$VERSION
    docker push $IMAGE_ID:$VERSION

Now this works if I want to build/push service1. You could duplicate those version lines and create an IMAGE_NAME variable for each service, but that doesn't look too good and it requires you update the workflow each time you add or remove a service.

Ideally, I only want to build and push the images if the version file has changed. It's more performant to build and push only when you need to vs. building and push on all changes, regardless if the services or if their version were modified.

The following line in the workflow already ensure that it only runs on pushes to the master branch:

on:
  push:
    branches:
      - master

By adding the paths key, you can also do matching on files or folders. I added the following match, so the workflow would only trigger on the master branch and if the change contains files that match the following pattern: src/**/VERSION. This pattern matches any VERSION file under the src folder and its subfolders. So this would match src/service1/VERSION, src/something/service-a/VERSION or src/VERSION.

on:
  push:
    branches:
      - master
    paths:
      - 'src/**/VERSION'

With this change I will ensure the workflow only runs if any of the VERSION files were updated, however, I would still need to know which version file was updated, so I know which service I need to build.

To figure that out, I used the git diff-tree command that looks like this:

git diff-tree --no-commit-id --name-only -r ${{ github.sha }}

The github.sha is the commit SHA that triggered the workflow to run. The no-commit-id and name-only flags ensure only the file paths are displayed - no commit IDs, and -r will recurse into sub-trees.

Let's look at a couple of examples on how this command works. If I use SHA 28e8761 as an example, here's how different flags control the output of the diff-tree command:

# Shows the full commit-id and folder name only (src)
$ git diff-tree 28e8761
28e8761d1f382d28ed9cfbf55407cfff8c3d0bea
:040000 040000 73b19d6e19192b77df6bbcf9750d19555af2763a 694fcd1447cec1f59fba2d3d21708890e02c03d7 M      src


# Don't show the commit ID
$ git diff-tree --no-commit-id 28e8761
:040000 040000 73b19d6e19192b77df6bbcf9750d19555af2763a 694fcd1447cec1f59fba2d3d21708890e02c03d7 M      src

# Only show the name
$ git diff-tree --no-commit-id --name-only 28e8761
src


# And recurse into the subtree
$ git diff-tree --no-commit-id --name-only -r 28e8761
src/service1/VERSION

This looks great! And so I thought. The issue is that if you have multiple commits (assuming you aren't squashing them) the diff-tree will only look at the last one. You need to provide another parameter to the diff-tree to tell it which commit to compare it to - that would be the last merge to the branch. So the first parameter is the last commit and the second one is the last merge to that branch, so the command would output changed files between those two trees.

If we assume the last merge to master is SHA d158d52, the command and its output would be this:

$ git diff-tree --no-commit-id --name-only -r 28e8761 d158d52
README.md
src/service1/VERSION
src/service2/VERSION

The second SHA value can be obtained from the ${{ github.event.before }} value, so at least you don't have to do more git magic. With this you get all files that have changed, and to only get the VERSION files, just use grep:

$ git diff-tree --no-commit-id --name-only -r 28e8761 d158d52 | grep "VERSION"
README.md
src/service1/VERSION
src/service2/VERSION

Note

Note that this could be improved, as the grep matches all lines containing the VERSION string and that could be other files as well.

Putting this together into a for loop, I ended up with this:

for versionFilePath in $(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} ${{ github.event.before }} | grep "VERSION");
do
  # Do the magic here!
done;

Next, I needed the folder name where the service lives (e.g. src/service1/) and the service name which I am using for the image name (service1).

To get the folder, you can use parameter expansion/substitution:

# If versionFilePath is "src/service1/VERSION", folder variable value will be "src/service1"
folder=$(versionFilePath%"/VERSION")

Using the % you can strip the string in quotes "/VERSION" from the original variable (versionFilePath). I did something similar to get the image name (or the folder name):

Note

Note: you can probably use cut, rev, tr and bunch of other commands as well.

The above command trims everything from the $folder variable until it hits the / character. Which means I am left with the last folder name in the path.

After some testing and trying stuff out, I ended up with the following workflow file:

name: Docker

on:
  push:
    branches:
      - master
    paths:
      - 'src/**/VERSION'
jobs:
  push:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Log into registry
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ secrets.DOCKER_REGISTRY_URL }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

      - name: Build and push the images
        run: |
          for versionFilePath in $(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} ${{ github.event.before }} | grep "VERSION");
          do
            folder=${versionFilePath%"/VERSION"}
            IMAGE_NAME=${folder##*/}

            tmpName="image-$RANDOM"
            docker build $folder --file $folder/Dockerfile --tag $tmpName
            IMAGE_ID=${{ secrets.DOCKER_REGISTRY_URL }}/${{ secrets.REPOSITORY }}/$IMAGE_NAME
            VERSION=$(cat $versionFilePath)

            echo IMAGE_ID=$IMAGE_ID
            echo VERSION=$VERSION

            docker tag $tmpName $IMAGE_ID:$VERSION
            docker push $IMAGE_ID:$VERSION
          done;

Once I get the values I need, I use docker build to build the image from the folder and I use the tmpName for the temporary image name. Next, I tag the temporary image name with the 'real' image name and version ($IMAGE_ID:$VERSION) and push that same image to the registry.

One thing I forgot to mention is the fetch-depth setting on the checkout action:

- uses: actions/checkout@v2
  with:
    fetch-depth: 0

For a while, I couldn't get the diff-tree command to work and it took a bit to figure out why. The reason was the fetch-depth setting. By default, the fetch-depth is set to 1 and this translates to fetching only 1 commit (this is done to improve the performance). Changing the value to 0 (fetch all history) fixed the issue with diff-tree.