In the world of serverless computing, AWS Lambda functions have become essential building blocks for modern applications. As your serverless architecture grows, managing the complete lifecycle of these functions becomes increasingly important. This post explores how GitHub can serve as the foundation for efficient Lambda lifecycle management, with a focus on path-based deployment workflows that maintain function independence.

Organizing Your Repository Structure

A well-organized repository structure is key to managing serverless functions effectively:

/
├── .github/
│   └── workflows/
│       ├── function-a-deploy.yml
│       ├── function-b-deploy.yml
│       └── function-c-deploy.yml
├── src/
│   ├── function-a/
│   │   ├── lambda_function.py
│   │   ├── requirements.txt
│   │   └── tests/
│   ├── function-b/
│   │   ├── lambda_function.py
│   │   ├── requirements.txt
│   │   └── tests/
│   ├── function-c/
│   │   ├── lambda_function.py
│   │   ├── requirements.txt
│   │   └── tests/
│   └── common/
│       └── utils.py
└── README.md

This structure provides:

  • Clear separation between individual functions
  • Dedicated workflows for each component
  • Centralized location for shared code
  • A logical organization that makes it easy to navigate and understand the codebase

Path-Based Deployment Workflows

The key to effective Lambda lifecycle management is implementing independent deployment workflows for each function. This ensures changes to one function don’t trigger unnecessary deployments of other components.

Here’s an example workflow for function-a:

# .github/workflows/function-a-deploy.yml
name: Deploy Function A

on:
  push:
    branches:
      - main
    paths:
      - 'src/function-a/**'
      - '.github/workflows/function-a-deploy.yml'
  pull_request:
    paths:
      - 'src/function-a/**'

jobs:
  test-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'

      - name: Install dependencies
        run: |
          cd src/function-a
          python -m pip install --upgrade pip
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          pip install pytest

      - name: Run tests
        run: |
          cd src/function-a
          python -m pytest tests/

      - name: Configure AWS credentials
        if: github.event_name == 'push' && github.ref == 'refs/heads/main'
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Package and deploy
        if: github.event_name == 'push' && github.ref == 'refs/heads/main'
        run: |
          cd src/function-a
          mkdir -p package
          pip install -r requirements.txt --target ./package
          cp lambda_function.py ./package/
          cd package
          zip -r ../function.zip .
          cd ..
          aws lambda update-function-code \\
            --function-name function-a \\
            --zip-file fileb://function.zip

Benefits of the Path-Based Approach

This deployment strategy offers several key advantages:

  1. Independent Deployment Cycles: Each function follows its own release schedule, allowing teams to deploy changes frequently without affecting other components.
  2. Reduced Risk: Issues in one deployment don’t impact other functions, containing potential problems to a smaller scope.
  3. Accelerated Development: Developers can work on different functions simultaneously without waiting for others to complete their work.
  4. Clear Ownership: Teams can take responsibility for specific functions, with GitHub’s CODEOWNERS file helping to enforce code review requirements.
  5. Streamlined Rollbacks: If a deployment causes issues, only the affected function needs to be rolled back, not the entire application.

Path Specification Best Practices

When setting up path-based workflows, consider these best practices:

  • Be Specific: Include only the paths directly related to the function:
paths:
  - 'src/function-a/**'
  - '.github/workflows/function-a-deploy.yml'
  • Account for Shared Resources: If functions depend on common utilities, trigger deployments when those resources change:
paths:
  - 'src/function-a/**'
  - 'src/common/**'  # Shared code
  - '.github/workflows/function-a-deploy.yml'
  • Include Workflow Self-Reference: Add the workflow file itself to the path specification so that workflow changes trigger a new deployment.
  • Cover Test Files: Ensure test files are included in the path specification, so that test changes also trigger the workflow.

Deployment Process Breakdown

The deployment process consists of several key steps:

  1. Checkout Code: Pull the latest code from the repository.
  2. Set Up Environment: Install the required runtime (Python in our example).
  3. Install Dependencies: Install the function-specific dependencies defined in requirements.txt.
  4. Run Tests: Execute the function’s test suite to ensure code quality.
  5. Configure AWS Credentials: Set up secure access to AWS services.
  6. Package Function: Create a deployment package with all dependencies and function code.
  7. Deploy to AWS: Update the Lambda function code in AWS.

This process ensures that each function is properly tested before deployment and includes all necessary dependencies.

Handling Shared Code

When functions share common code, you need a strategy to ensure changes to shared code are properly propagated:

  • Option 1: Deploy All Affected Functions: Update all workflows to trigger when shared code changes:
paths:
  - 'src/function-a/**'
  - 'src/common/**'
  - '.github/workflows/function-a-deploy.yml'
  • Option 2: Leverage Lambda Layers: Create a separate Layer for shared code with its own deployment workflow.
  • Option 3: Bundle Shared Code: Copy the shared code into each function’s deployment package:
# In the package and deploy step
mkdir -p package
pip install -r requirements.txt --target ./package
cp lambda_function.py ./package/
cp -r ../common ./package/  # Copy shared code
cd package
zip -r ../function.zip .

Conclusion

GitHub provides a solid foundation for managing AWS Lambda functions with path-based workflows, enabling independent deployments and reducing risk. By organizing your repository thoughtfully and automating key deployment steps, you can streamline the development process while maintaining code quality and stability. This approach balances function autonomy with shared best practices, making serverless application management more efficient and scalable.